From 000642781b04d19f36cbd94e987f96362e7ef4e8 Mon Sep 17 00:00:00 2001 From: Timothy Quilling Date: Tue, 30 Dec 2025 00:10:36 -0500 Subject: [PATCH] chore: rearranging parakeet-db --- .../src/database_writer/bulk_processor.rs | 16 +- consumer/src/database_writer/bulk_types.rs | 28 +- .../database_writer/operations/executor.rs | 10 +- .../operations/handlers/follow.rs | 2 +- .../operations/handlers/like.rs | 4 +- .../operations/handlers/post.rs | 14 +- .../operations/handlers/repost.rs | 6 +- .../operations/notifications.rs | 4 +- .../database_writer/reference_extraction.rs | 16 +- consumer/src/database_writer/workers_tap.rs | 12 +- consumer/src/db/actor.rs | 2 +- consumer/src/db/bulk_resolve/mod.rs | 16 +- consumer/src/db/composite_builders.rs | 12 +- consumer/src/db/gates/queries.rs | 8 +- consumer/src/db/id_resolution.rs | 4 +- consumer/src/db/labels.rs | 4 +- consumer/src/db/operations/actor.rs | 16 +- consumer/src/db/operations/community.rs | 2 +- consumer/src/db/operations/feed/feedgen.rs | 2 +- consumer/src/db/operations/feed/helpers.rs | 10 +- consumer/src/db/operations/feed/like.rs | 10 +- consumer/src/db/operations/feed/post.rs | 18 +- consumer/src/db/operations/feed/postgate.rs | 4 +- consumer/src/db/operations/feed/repost.rs | 6 +- consumer/src/db/operations/feed/threadgate.rs | 4 +- consumer/src/db/operations/graph.rs | 8 +- consumer/src/db/operations/labeler.rs | 4 +- consumer/src/db/operations/starter_pack.rs | 4 +- consumer/src/db/record_exists/mod.rs | 2 +- consumer/src/label_indexer/mod.rs | 4 +- consumer/src/utils.rs | 6 +- consumer/tests/bulk_copy_queries_test.rs | 20 +- consumer/tests/record_exists_queries_test.rs | 2 +- .../{composite_types.rs => composite/mod.rs} | 0 .../src/{ => infrastructure}/schema.rs | 0 .../{types.rs => infrastructure/types/mod.rs} | 0 parakeet-db/src/models.rs | 766 ------------------ .../src/{at_uri_util.rs => utils/at_uri.rs} | 0 parakeet-db/src/{cid_util.rs => utils/cid.rs} | 0 parakeet-db/src/{ => utils}/compression.rs | 0 parakeet-db/src/{tid_util.rs => utils/tid.rs} | 0 parakeet/src/common/cache_listener.rs | 2 +- parakeet/src/common/helpers.rs | 2 +- parakeet/src/entities/converters/post.rs | 6 +- parakeet/src/entities/converters/profile.rs | 20 +- parakeet/src/entities/core/feedgen.rs | 4 +- parakeet/src/entities/core/list.rs | 10 +- parakeet/src/entities/core/notification.rs | 4 +- parakeet/src/entities/core/post.rs | 40 +- parakeet/src/entities/core/profile.rs | 22 +- parakeet/src/entities/core/starterpack.rs | 14 +- parakeet/src/entities/ext/actor.rs | 4 +- parakeet/src/entities/ext/post.rs | 4 +- parakeet/src/xrpc/app_bsky/bookmark.rs | 10 +- .../src/xrpc/app_bsky/feed/get_timeline.rs | 14 +- parakeet/src/xrpc/app_bsky/feed/likes.rs | 6 +- .../src/xrpc/app_bsky/feed/posts/queries.rs | 6 +- .../src/xrpc/app_bsky/feed/posts/threads.rs | 8 +- parakeet/src/xrpc/app_bsky/feed/search.rs | 4 +- parakeet/src/xrpc/app_bsky/graph/lists.rs | 4 +- parakeet/src/xrpc/app_bsky/graph/relations.rs | 8 +- .../src/xrpc/app_bsky/graph/starter_packs.rs | 2 +- .../src/xrpc/app_bsky/graph/thread_mutes.rs | 4 +- parakeet/src/xrpc/app_bsky/notification.rs | 12 +- .../src/xrpc/app_bsky/unspecced/handlers.rs | 2 +- .../unspecced/thread_v2/other_replies.rs | 8 +- .../unspecced/thread_v2/thread_builder.rs | 12 +- parakeet/src/xrpc/com_atproto/repo.rs | 12 +- .../src/xrpc/community_lexicon/bookmarks.rs | 2 +- 69 files changed, 262 insertions(+), 1030 deletions(-) rename parakeet-db/src/{composite_types.rs => composite/mod.rs} (100%) rename parakeet-db/src/{ => infrastructure}/schema.rs (100%) rename parakeet-db/src/{types.rs => infrastructure/types/mod.rs} (100%) delete mode 100644 parakeet-db/src/models.rs rename parakeet-db/src/{at_uri_util.rs => utils/at_uri.rs} (100%) rename parakeet-db/src/{cid_util.rs => utils/cid.rs} (100%) rename parakeet-db/src/{ => utils}/compression.rs (100%) rename parakeet-db/src/{tid_util.rs => utils/tid.rs} (100%) diff --git a/consumer/src/database_writer/bulk_processor.rs b/consumer/src/database_writer/bulk_processor.rs index 1b025892..d1f8460e 100644 --- a/consumer/src/database_writer/bulk_processor.rs +++ b/consumer/src/database_writer/bulk_processor.rs @@ -295,7 +295,7 @@ pub async fn process_bulk_records( if let RecordTypes::AppBskyFeedPost(ref post) = record { // Resolve parent author let parent_author = if let Some(ref reply) = post.reply { - let parent_did = parakeet_db::at_uri_util::extract_did(&reply.parent.uri); + let parent_did = parakeet_db::utils::at_uri::extract_did(&reply.parent.uri); if let Some(did) = parent_did { let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; Some(aid) @@ -309,7 +309,7 @@ pub async fn process_bulk_records( // Resolve root author (if different from parent) let root_author = if let Some(ref reply) = post.reply { if reply.root.uri != reply.parent.uri { - let root_did = parakeet_db::at_uri_util::extract_did(&reply.root.uri); + let root_did = parakeet_db::utils::at_uri::extract_did(&reply.root.uri); if let Some(did) = root_did { let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; Some(aid) @@ -333,7 +333,7 @@ pub async fn process_bulk_records( }; if let Some(uri) = quote_uri { - let quoted_did = parakeet_db::at_uri_util::extract_did(uri); + let quoted_did = parakeet_db::utils::at_uri::extract_did(uri); if let Some(did) = quoted_did { let (aid, _, _) = crate::db::operations::feed::get_actor_id(conn, did).await?; Some(aid) @@ -372,7 +372,7 @@ pub async fn process_bulk_records( // Resolve via_repost natural key if present (for likes and reposts that came via a repost) let via_repost_key = if let (Some(via_uri), Some(via_cid)) = (&refs.via_uri, &refs.via_cid) { // Extract the DID and rkey from the via URI - if let Some((via_did, via_rkey, _collection)) = parakeet_db::at_uri_util::parse_at_uri(via_uri) { + if let Some((via_did, via_rkey, _collection)) = parakeet_db::utils::at_uri::parse_at_uri(via_uri) { // Ensure the via repost actor exists let (via_actor_id, _, _) = crate::db::operations::feed::get_actor_id(conn, via_did).await?; @@ -488,7 +488,7 @@ async fn process_likes_bulk( let labeler_dids: Vec<&str> = labeler_likes.iter() .filter_map(|&i| { let uri = &likes[i].record.subject.uri; - parakeet_db::at_uri_util::extract_did(uri) + parakeet_db::utils::at_uri::extract_did(uri) }) .collect(); let resolved_labelers = if !labeler_dids.is_empty() { @@ -568,7 +568,7 @@ async fn process_likes_bulk( } else if subject_uri.contains("/app.bsky.labeler.service/") { // Labeler like - only include if labeler exists (no auto-stubbing) // Extract DID from labeler AT URI - if let Some(labeler_did) = parakeet_db::at_uri_util::extract_did(subject_uri) { + if let Some(labeler_did) = parakeet_db::utils::at_uri::extract_did(subject_uri) { if let Some(&labeler_actor_id) = resolved_labelers.get(labeler_did) { labeler_like_data.push(LabelerLikeCopyData { actor_id, @@ -697,7 +697,7 @@ async fn process_reposts_bulk( // Get CID digest (real CID for reposts) let cid_bytes = repost.cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_else(|| eyre::eyre!("Invalid CID for repost"))?; // Resolve via_repost natural keys if present @@ -857,7 +857,7 @@ async fn process_posts_bulk( // Extract CID digest let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("Valid CID should have digest"); // Resolve parent and root post natural keys diff --git a/consumer/src/database_writer/bulk_types.rs b/consumer/src/database_writer/bulk_types.rs index 5dad546c..71a06267 100644 --- a/consumer/src/database_writer/bulk_types.rs +++ b/consumer/src/database_writer/bulk_types.rs @@ -194,23 +194,23 @@ pub struct PostCopyData { pub tokens: Vec, // Search tokens // Composite type columns (stored directly in posts table) - pub ext_embed: Option, - pub video_embed: Option, - pub image_1: Option, - pub image_2: Option, - pub image_3: Option, - pub image_4: Option, + pub ext_embed: Option, + pub video_embed: Option, + pub image_1: Option, + pub image_2: Option, + pub image_3: Option, + pub image_4: Option, pub embedded_post_actor_id: Option, // For quote posts (natural key part 1) pub embedded_post_rkey: Option, // For quote posts (natural key part 2) pub record_detached: Option, // For quote posts - pub facet_1: Option, - pub facet_2: Option, - pub facet_3: Option, - pub facet_4: Option, - pub facet_5: Option, - pub facet_6: Option, - pub facet_7: Option, - pub facet_8: Option, + pub facet_1: Option, + pub facet_2: Option, + pub facet_3: Option, + pub facet_4: Option, + pub facet_5: Option, + pub facet_6: Option, + pub facet_7: Option, + pub facet_8: Option, pub mentions: Option>>, // Mention actor IDs array } diff --git a/consumer/src/database_writer/operations/executor.rs b/consumer/src/database_writer/operations/executor.rs index 216b3eb4..a3e75743 100644 --- a/consumer/src/database_writer/operations/executor.rs +++ b/consumer/src/database_writer/operations/executor.rs @@ -425,7 +425,7 @@ pub async fn execute_operation( // Parse reason_subject URI early for thread mute check and notification // Format: at://did:plc:xyz/app.bsky.feed.post/rkey let subject_rkey_str = reason_subject.split('/').next_back().unwrap_or(""); - let subject_rkey_i64 = match parakeet_db::tid_util::decode_tid(subject_rkey_str) { + let subject_rkey_i64 = match parakeet_db::utils::tid::decode_tid(subject_rkey_str) { Ok(rkey) => rkey, Err(e) => { tracing::warn!("Invalid subject TID in reason_subject: {} - {}", subject_rkey_str, e); @@ -486,7 +486,7 @@ pub async fn execute_operation( // Create PostgreSQL notification for this ancestor // Extract rkey from reply_uri (at://did/app.bsky.feed.post/rkey) let reply_rkey_str = reply_uri.split('/').next_back().unwrap_or(""); - let reply_rkey_i64 = match parakeet_db::tid_util::decode_tid(reply_rkey_str) { + let reply_rkey_i64 = match parakeet_db::utils::tid::decode_tid(reply_rkey_str) { Ok(rkey) => rkey, Err(e) => { tracing::warn!("Invalid reply TID: {} - {}", reply_rkey_str, e); @@ -504,7 +504,7 @@ pub async fn execute_operation( let cid_digest = match Cid::try_from(cid.as_str()) { Ok(cid_obj) => { let cid_bytes = cid_obj.to_bytes(); - match parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) { + match parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes) { Some(digest) => digest, None => { tracing::warn!("Invalid CID digest for CID: {}", cid); @@ -940,7 +940,7 @@ pub async fn execute_operation( } CollectionType::BskyFeedGen => { // Feedgens use arbitrary string rkeys - extract from at_uri - let rkey_str = parakeet_db::at_uri_util::extract_rkey(&at_uri) + let rkey_str = parakeet_db::utils::at_uri::extract_rkey(&at_uri) .ok_or_else(|| eyre::eyre!("Invalid at_uri: missing rkey"))?; let rows = db::feedgen_delete(conn, actor_id, rkey_str).await?; if rows > 0 { @@ -955,7 +955,7 @@ pub async fn execute_operation( } CollectionType::BskyList => { // Lists use arbitrary string rkeys - extract from at_uri - let rkey_str = parakeet_db::at_uri_util::extract_rkey(&at_uri) + let rkey_str = parakeet_db::utils::at_uri::extract_rkey(&at_uri) .ok_or_else(|| eyre::eyre!("Invalid at_uri: missing rkey"))?; let rows = db::list_delete(conn, actor_id, rkey_str).await?; diff --git a/consumer/src/database_writer/operations/handlers/follow.rs b/consumer/src/database_writer/operations/handlers/follow.rs index 31700a75..e3c8ae48 100644 --- a/consumer/src/database_writer/operations/handlers/follow.rs +++ b/consumer/src/database_writer/operations/handlers/follow.rs @@ -39,7 +39,7 @@ pub fn handle_follow( let target_actor_id = ctx.subject_actor_id.expect("subject_actor_id required for notification"); // Extract CID digest (32 bytes) - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&ctx.cid.to_bytes()) + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&ctx.cid.to_bytes()) .expect("Valid CID should have digest"); operations.push(crate::database_writer::operations::create_follow_notification( diff --git a/consumer/src/database_writer/operations/handlers/like.rs b/consumer/src/database_writer/operations/handlers/like.rs index 9aa971f0..66eb5290 100644 --- a/consumer/src/database_writer/operations/handlers/like.rs +++ b/consumer/src/database_writer/operations/handlers/like.rs @@ -39,8 +39,8 @@ pub fn handle_like( // Create notification for the post author (if post author actor is known) if let Some(post_author_actor_id) = ctx.subject_actor_id { // Extract rkey from the post URI and convert to i64 - if let Some(post_rkey_str) = parakeet_db::at_uri_util::extract_rkey(&subject_uri) { - if let Ok(post_rkey_i64) = parakeet_db::tid_util::decode_tid(post_rkey_str) { + if let Some(post_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(&subject_uri) { + if let Ok(post_rkey_i64) = parakeet_db::utils::tid::decode_tid(post_rkey_str) { // Generate synthetic CID digest operations.push(crate::database_writer::operations::create_like_notification( rkey_i64, // Already converted above diff --git a/consumer/src/database_writer/operations/handlers/post.rs b/consumer/src/database_writer/operations/handlers/post.rs index d002a8af..d5c659d9 100644 --- a/consumer/src/database_writer/operations/handlers/post.rs +++ b/consumer/src/database_writer/operations/handlers/post.rs @@ -73,7 +73,7 @@ pub fn handle_post( .expect("TID validation passed, conversion should succeed"); // Extract CID digest (32 bytes) - will be used for notifications - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&cid.to_bytes()) + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&cid.to_bytes()) .expect("Valid CID should have digest"); // Create notification for the quoted post author @@ -82,8 +82,8 @@ pub fn handle_post( // Don't notify if quoting self if quoted_author_id != actor_id { // Extract rkey from quoted post URI and convert to i64 - if let Some(quoted_rkey_str) = parakeet_db::at_uri_util::extract_rkey(embed_uri) { - if let Ok(quoted_rkey_i64) = parakeet_db::tid_util::decode_tid(quoted_rkey_str) { + if let Some(quoted_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(embed_uri) { + if let Ok(quoted_rkey_i64) = parakeet_db::utils::tid::decode_tid(quoted_rkey_str) { operations.push(crate::database_writer::operations::create_quote_notification( rkey_i64, // Already converted above actor_id, @@ -126,13 +126,13 @@ pub fn handle_post( let mut notified_actor_ids = std::collections::HashSet::new(); // Extract rkeys from parent and root URIs and convert to i64 - let parent_rkey_str = parakeet_db::at_uri_util::extract_rkey(parent_uri); - let root_rkey_str = maybe_root.as_ref().and_then(|uri| parakeet_db::at_uri_util::extract_rkey(uri)); + let parent_rkey_str = parakeet_db::utils::at_uri::extract_rkey(parent_uri); + let root_rkey_str = maybe_root.as_ref().and_then(|uri| parakeet_db::utils::at_uri::extract_rkey(uri)); if let Some(parent_rkey_str) = parent_rkey_str { - if let Ok(parent_rkey_i64) = parakeet_db::tid_util::decode_tid(parent_rkey_str) { + if let Ok(parent_rkey_i64) = parakeet_db::utils::tid::decode_tid(parent_rkey_str) { // Convert root rkey to i64 if present - let root_rkey_i64 = root_rkey_str.and_then(|s| parakeet_db::tid_util::decode_tid(s).ok()); + let root_rkey_i64 = root_rkey_str.and_then(|s| parakeet_db::utils::tid::decode_tid(s).ok()); // Always notify direct parent author if let Some(parent_author_id) = post_ctx.parent_author_actor_id { diff --git a/consumer/src/database_writer/operations/handlers/repost.rs b/consumer/src/database_writer/operations/handlers/repost.rs index 0842c467..aa3a5614 100644 --- a/consumer/src/database_writer/operations/handlers/repost.rs +++ b/consumer/src/database_writer/operations/handlers/repost.rs @@ -39,10 +39,10 @@ pub fn handle_repost( // Create notification for the post author (if post author actor is known) if let Some(post_author_actor_id) = ctx.subject_actor_id { // Extract rkey from the post URI and convert to i64 - if let Some(post_rkey_str) = parakeet_db::at_uri_util::extract_rkey(&subject_uri) { - if let Ok(post_rkey_i64) = parakeet_db::tid_util::decode_tid(post_rkey_str) { + if let Some(post_rkey_str) = parakeet_db::utils::at_uri::extract_rkey(&subject_uri) { + if let Ok(post_rkey_i64) = parakeet_db::utils::tid::decode_tid(post_rkey_str) { // Extract CID digest (32 bytes) - let cid_digest = parakeet_db::cid_util::cid_to_digest_owned(&ctx.cid.to_bytes()) + let cid_digest = parakeet_db::utils::cid::cid_to_digest_owned(&ctx.cid.to_bytes()) .expect("Valid CID should have digest"); operations.push(crate::database_writer::operations::create_repost_notification( diff --git a/consumer/src/database_writer/operations/notifications.rs b/consumer/src/database_writer/operations/notifications.rs index a1098ec5..b76d903a 100644 --- a/consumer/src/database_writer/operations/notifications.rs +++ b/consumer/src/database_writer/operations/notifications.rs @@ -9,7 +9,7 @@ use super::DatabaseOperation; use chrono::{DateTime, Utc}; -use parakeet_db::notifications::reasons; +use parakeet_db::domain::notification::reasons; /// Create a notification for a like /// @@ -35,7 +35,7 @@ pub fn create_like_notification( created_at: DateTime, ) -> DatabaseOperation { // Generate synthetic CID digest for the like - let cid_digest = parakeet_db::cid_util::generate_like_cid_digest(liker_actor_id, like_rkey); + let cid_digest = parakeet_db::utils::cid::generate_like_cid_digest(liker_actor_id, like_rkey); DatabaseOperation::InsertNotification { recipient_actor_id: post_author_actor_id, diff --git a/consumer/src/database_writer/reference_extraction.rs b/consumer/src/database_writer/reference_extraction.rs index b117508e..8ae13595 100644 --- a/consumer/src/database_writer/reference_extraction.rs +++ b/consumer/src/database_writer/reference_extraction.rs @@ -126,7 +126,7 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { // Like: extract author DID from liked post URI and via repost URI // NOTE: Likes don't have subject_actor_id FK in DB, but we need it for notifications RecordTypes::AppBskyFeedLike(rec) => { - let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { + let mut refs = if let Some(subject_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { RecordReferences::with_subject(subject_did.to_string()) } else { RecordReferences::empty() @@ -144,7 +144,7 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { // Repost: extract author DID from reposted post URI and via repost URI // NOTE: Reposts don't have subject_actor_id FK in DB, but we need it for notifications RecordTypes::AppBskyFeedRepost(rec) => { - let mut refs = if let Some(subject_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { + let mut refs = if let Some(subject_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { RecordReferences::with_subject(subject_did.to_string()) } else { RecordReferences::empty() @@ -165,9 +165,9 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { // Extract reply parent and root authors (for notifications) if let Some(reply) = &rec.reply { - refs.parent_author_did = parakeet_db::at_uri_util::extract_did(&reply.parent.uri) + refs.parent_author_did = parakeet_db::utils::at_uri::extract_did(&reply.parent.uri) .map(|s| s.to_string()); - refs.root_author_did = parakeet_db::at_uri_util::extract_did(&reply.root.uri) + refs.root_author_did = parakeet_db::utils::at_uri::extract_did(&reply.root.uri) .map(|s| s.to_string()); } @@ -177,12 +177,12 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { match bsky_embed { AppBskyEmbed::Record(record_embed) => { refs.quoted_author_did = - parakeet_db::at_uri_util::extract_did(&record_embed.record.uri) + parakeet_db::utils::at_uri::extract_did(&record_embed.record.uri) .map(|s| s.to_string()); } AppBskyEmbed::RecordWithMedia(rwm) => { refs.quoted_author_did = - parakeet_db::at_uri_util::extract_did(&rwm.record.uri) + parakeet_db::utils::at_uri::extract_did(&rwm.record.uri) .map(|s| s.to_string()); } _ => {} @@ -201,7 +201,7 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { // ListBlock: extract list owner DID RecordTypes::AppBskyGraphListBlock(rec) => { let mut dids = Vec::new(); - if let Some(list_did) = parakeet_db::at_uri_util::extract_did(rec.subject.uri.as_str()) { + if let Some(list_did) = parakeet_db::utils::at_uri::extract_did(rec.subject.uri.as_str()) { dids.push(list_did.to_string()); } RecordReferences::with_additional(dids) @@ -213,7 +213,7 @@ pub fn extract_references(record: &RecordTypes) -> RecordReferences { if let Some(rules) = &rec.allow { for rule in rules { if let ThreadgateRule::List { list } = rule { - if let Some(list_did) = parakeet_db::at_uri_util::extract_did(list) { + if let Some(list_did) = parakeet_db::utils::at_uri::extract_did(list) { dids.push(list_did.to_string()); } } diff --git a/consumer/src/database_writer/workers_tap.rs b/consumer/src/database_writer/workers_tap.rs index 4cbafaf4..d74fcfb8 100644 --- a/consumer/src/database_writer/workers_tap.rs +++ b/consumer/src/database_writer/workers_tap.rs @@ -339,11 +339,9 @@ async fn resolve_and_process_event( // For posts, resolve parent/root/quoted authors let (parent_author_actor_id, root_author_actor_id, quoted_author_actor_id, mentioned_actor_ids) = if let crate::relay::types::RecordTypes::AppBskyFeedPost(ref post) = *record { - use parakeet_db::at_uri_util; - // Resolve parent author let parent_author = if let Some(ref reply) = post.reply { - if let Some(did) = at_uri_util::extract_did(&reply.parent.uri) { + if let Some(did) = utils::at_uri::extract_did(&reply.parent.uri) { let (aid, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; Some(aid) } else { @@ -356,7 +354,7 @@ async fn resolve_and_process_event( // Resolve root author let root_author = if let Some(ref reply) = post.reply { if reply.root.uri != reply.parent.uri { - if let Some(did) = at_uri_util::extract_did(&reply.root.uri) { + if let Some(did) = utils::at_uri::extract_did(&reply.root.uri) { let (aid, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; Some(aid) } else { @@ -375,7 +373,7 @@ async fn resolve_and_process_event( match bsky_embed { AppBskyEmbed::Record(record_embed) => { // Extract DID from the quoted post URI - if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_embed.record.uri) { + if let Some(did) = parakeet_db::utils::at_uri::extract_did(&record_embed.record.uri) { let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; Some(actor_id) } else { @@ -384,7 +382,7 @@ async fn resolve_and_process_event( }, AppBskyEmbed::RecordWithMedia(record_with_media) => { // Extract DID from the quoted post URI - if let Some(did) = parakeet_db::at_uri_util::extract_did(&record_with_media.record.uri) { + if let Some(did) = parakeet_db::utils::at_uri::extract_did(&record_with_media.record.uri) { let (actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, did).await?; Some(actor_id) } else { @@ -422,7 +420,7 @@ async fn resolve_and_process_event( // Resolve via_repost if present let via_repost_key = if let (Some(via_uri), Some(via_cid)) = (&refs.via_uri, &refs.via_cid) { - if let Some((via_did, via_rkey, _)) = parakeet_db::at_uri_util::parse_at_uri(via_uri) { + if let Some((via_did, via_rkey, _)) = parakeet_db::utils::at_uri::parse_at_uri(via_uri) { let (via_actor_id, _, _) = crate::db::operations::feed::get_actor_id(&mut conn, via_did).await?; let (key, _) = crate::db::operations::feed::get_repost_id( &mut conn, diff --git a/consumer/src/db/actor.rs b/consumer/src/db/actor.rs index e00ab28e..88a46e63 100644 --- a/consumer/src/db/actor.rs +++ b/consumer/src/db/actor.rs @@ -102,7 +102,7 @@ pub async fn actor_set_repo_state( cid: Cid, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); // Use consolidated ActorUpdate API for repo state update diff --git a/consumer/src/db/bulk_resolve/mod.rs b/consumer/src/db/bulk_resolve/mod.rs index 31327ea5..8c06af52 100644 --- a/consumer/src/db/bulk_resolve/mod.rs +++ b/consumer/src/db/bulk_resolve/mod.rs @@ -90,9 +90,9 @@ pub async fn resolve_post_uris_bulk( let mut dids_set: std::collections::HashSet = std::collections::HashSet::new(); for uri in at_uris { - let did = parakeet_db::at_uri_util::extract_did(uri) + let did = parakeet_db::utils::at_uri::extract_did(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", uri, e))?; @@ -253,9 +253,9 @@ pub async fn resolve_feedgen_uris_bulk( let mut dids_set: std::collections::HashSet = std::collections::HashSet::new(); for uri in at_uris { - let did = parakeet_db::at_uri_util::extract_did(uri) + let did = parakeet_db::utils::at_uri::extract_did(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; uri_to_did_rkey.insert(uri.to_string(), (did.to_string(), rkey.to_string())); @@ -380,9 +380,9 @@ pub async fn resolve_repost_uris_bulk( let mut dids_set: std::collections::HashSet = std::collections::HashSet::new(); for uri in at_uris { - let did = parakeet_db::at_uri_util::extract_did(uri) + let did = parakeet_db::utils::at_uri::extract_did(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", uri, e))?; @@ -526,9 +526,9 @@ pub async fn resolve_list_uris_bulk( let mut dids_set: std::collections::HashSet = std::collections::HashSet::new(); for uri in at_uris { - let did = parakeet_db::at_uri_util::extract_did(uri) + let did = parakeet_db::utils::at_uri::extract_did(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", uri))?; uri_to_did_rkey.insert(uri.to_string(), (did.to_string(), rkey.to_string())); diff --git a/consumer/src/db/composite_builders.rs b/consumer/src/db/composite_builders.rs index a915ec90..b09c490e 100644 --- a/consumer/src/db/composite_builders.rs +++ b/consumer/src/db/composite_builders.rs @@ -3,7 +3,7 @@ //! These functions transform AT Protocol records into the denormalized composite type //! structures used in the posts table. -use parakeet_db::composite_types::{ExtEmbed, VideoEmbed, ImageEmbed, FacetEmbed}; +use parakeet_db::composite::{ExtEmbed, VideoEmbed, ImageEmbed, FacetEmbed}; use parakeet_db::types::{ImageMimeType, VideoMimeType, FacetType}; use crate::types::records::{AppBskyEmbedImages, AppBskyEmbedVideo, AppBskyEmbedExternal}; use jacquard_api::app_bsky::richtext::facet::{Facet as FacetMain, FacetFeaturesItem}; @@ -19,7 +19,7 @@ pub fn build_ext_embed(embed: &AppBskyEmbedExternal) -> Option { let cid_str = thumb.cid().as_str(); let cid_parsed = cid_str.parse::().ok(); let cid_bytes = cid_parsed.map(|c| c.to_bytes()); - let cid = cid_bytes.and_then(|b| parakeet_db::cid_util::cid_to_digest_owned(&b)); + let cid = cid_bytes.and_then(|b| parakeet_db::utils::cid::cid_to_digest_owned(&b)); (mime, cid) } else { (None, None) @@ -36,7 +36,7 @@ pub fn build_ext_embed(embed: &AppBskyEmbedExternal) -> Option { /// Build video embed composite from AppBskyEmbedVideo pub fn build_video_embed(embed: &AppBskyEmbedVideo) -> Option { - use parakeet_db::composite_types::VideoCaption; + use parakeet_db::composite::VideoCaption; use parakeet_db::types::{CaptionMimeType, LanguageCode}; let video = &embed.video; @@ -46,7 +46,7 @@ pub fn build_video_embed(embed: &AppBskyEmbedVideo) -> Option { let cid_str = video.cid().as_str(); let cid_parsed = cid_str.parse::().ok()?; let cid_bytes = cid_parsed.to_bytes(); - let cid = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes)?; + let cid = parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes)?; // Build caption fields (max 3) let mut captions = [None, None, None]; @@ -57,7 +57,7 @@ pub fn build_video_embed(embed: &AppBskyEmbedVideo) -> Option { let caption_cid_str = caption_data.file.cid().as_str(); let caption_cid_parsed = caption_cid_str.parse::().ok()?; let caption_cid_bytes = caption_cid_parsed.to_bytes(); - let caption_cid = parakeet_db::cid_util::cid_to_digest_owned(&caption_cid_bytes)?; + let caption_cid = parakeet_db::utils::cid::cid_to_digest_owned(&caption_cid_bytes)?; captions[idx] = Some(VideoCaption { lang, @@ -93,7 +93,7 @@ pub fn build_image_embeds(embed: &AppBskyEmbedImages) -> (Option, Op let cid_str = image.image.cid().as_str(); if let Ok(cid_parsed) = cid_str.parse::() { let cid_bytes = cid_parsed.to_bytes(); - if let Some(cid) = parakeet_db::cid_util::cid_to_digest_owned(&cid_bytes) { + if let Some(cid) = parakeet_db::utils::cid::cid_to_digest_owned(&cid_bytes) { images[idx] = Some(ImageEmbed { mime_type, cid, diff --git a/consumer/src/db/gates/queries.rs b/consumer/src/db/gates/queries.rs index 11bf245d..20b45675 100644 --- a/consumer/src/db/gates/queries.rs +++ b/consumer/src/db/gates/queries.rs @@ -160,9 +160,9 @@ pub async fn maintain_postgates_cached( // OPTIMIZATION 2: Resolve target post natural key directly from database let (target_post_actor_id, target_post_rkey) = { - let did = parakeet_db::at_uri_util::extract_did(post_uri) + let did = parakeet_db::utils::at_uri::extract_did(post_uri) .ok_or_else(|| eyre::eyre!("Invalid post URI: missing DID in {}", post_uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(post_uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(post_uri) .ok_or_else(|| eyre::eyre!("Invalid post URI: missing rkey in {}", post_uri))?; let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) .wrap_err_with(|| format!("Invalid TID encoding in rkey: {}", rkey))?; @@ -183,9 +183,9 @@ pub async fn maintain_postgates_cached( // OPTIMIZATION 3: Resolve detached post natural keys from database (batch) let mut detached_post_keys = Vec::new(); for uri in detached_uris { - let did = parakeet_db::at_uri_util::extract_did(uri) + let did = parakeet_db::utils::at_uri::extract_did(uri) .ok_or_else(|| eyre::eyre!("Invalid detached URI: missing DID in {}", uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .ok_or_else(|| eyre::eyre!("Invalid detached URI: missing rkey in {}", uri))?; let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) .wrap_err_with(|| format!("Invalid TID encoding in rkey: {}", rkey))?; diff --git a/consumer/src/db/id_resolution.rs b/consumer/src/db/id_resolution.rs index 254fb643..20bbc6dc 100644 --- a/consumer/src/db/id_resolution.rs +++ b/consumer/src/db/id_resolution.rs @@ -82,9 +82,9 @@ async fn resolve_at_uri_to_post_natural_key( at_uri: &str, ) -> Result<(i32, i64)> { // Parse AT URI: at://did/collection/rkey - let did = parakeet_db::at_uri_util::extract_did(at_uri) + let did = parakeet_db::utils::at_uri::extract_did(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; let rkey_i64 = parakeet_db::models::tid_to_i64(rkey) .map_err(|e| eyre::eyre!("Invalid TID in AT URI {}: {}", at_uri, e))?; diff --git a/consumer/src/db/labels.rs b/consumer/src/db/labels.rs index fd2486bd..2affd203 100644 --- a/consumer/src/db/labels.rs +++ b/consumer/src/db/labels.rs @@ -103,7 +103,7 @@ pub async fn maintain_self_labels( // AT URI format: at://{did}/collection/rkey // Actor profiles: at://{did}/app.bsky.actor.profile/self // Posts: at://{did}/app.bsky.feed.post/{rkey} - let (did, collection, rkey) = parakeet_db::at_uri_util::parse_at_uri(at_uri) + let (did, collection, rkey) = parakeet_db::utils::at_uri::parse_at_uri(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: {}", at_uri))?; // Build labels array values @@ -148,7 +148,7 @@ pub async fn maintain_self_labels( } else if collection == "app.bsky.feed.post" { // Update post labels array // Convert TID string to INT8 for rkey lookup - let rkey_i64 = parakeet_db::tid_util::decode_tid(rkey)?; + let rkey_i64 = parakeet_db::utils::tid::decode_tid(rkey)?; conn.execute( "UPDATE posts p diff --git a/consumer/src/db/operations/actor.rs b/consumer/src/db/operations/actor.rs index baa0d7ad..3e2d9c9d 100644 --- a/consumer/src/db/operations/actor.rs +++ b/consumer/src/db/operations/actor.rs @@ -19,7 +19,7 @@ pub async fn profile_upsert( // SCHEMA CHANGE: profiles table dropped, now UPDATE actors.profile_* columns // No advisory lock needed - simple UPDATE with PostgreSQL row-level locking let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); let avatar = blob_to_cid_bytes(rec.avatar.as_ref()); let banner = blob_to_cid_bytes(rec.banner.as_ref()); @@ -35,7 +35,7 @@ pub async fn profile_upsert( if collection == Some("app.bsky.feed.post") { // Extract and parse rkey - parakeet_db::at_uri_util::extract_rkey(uri) + parakeet_db::utils::at_uri::extract_rkey(uri) .and_then(|rkey_str| parakeet_db::models::tid_to_i64(rkey_str).ok()) } else { None // Not a post URI, skip @@ -53,8 +53,8 @@ pub async fn profile_upsert( if collection == Some("app.bsky.graph.starterpack") { // Parse URI components - let sp_did = parakeet_db::at_uri_util::extract_did(uri); - let sp_rkey = parakeet_db::at_uri_util::extract_rkey(uri) + let sp_did = parakeet_db::utils::at_uri::extract_did(uri); + let sp_rkey = parakeet_db::utils::at_uri::extract_rkey(uri) .and_then(|rkey_str| parakeet_db::models::tid_to_i64(rkey_str).ok()); if let (Some(sp_did), Some(sp_rkey)) = (sp_did, sp_rkey) { @@ -146,7 +146,7 @@ pub async fn status_upsert( // SCHEMA CHANGE: statuses table dropped, now UPDATE actors.status_* columns // No advisory lock needed - simple UPDATE with PostgreSQL row-level locking let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); let thumb = rec.embed.as_ref().and_then(|v| v.external.thumb.clone()); @@ -155,7 +155,7 @@ pub async fn status_upsert( let cid_str = v.cid(); let cid_parsed = cid_str.as_str().parse::().expect("Valid CID"); let cid_bytes = cid_parsed.to_bytes(); - parakeet_db::cid_util::cid_to_digest(&cid_bytes) + parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID") .to_vec() }); @@ -171,8 +171,8 @@ pub async fn status_upsert( if collection == Some("app.bsky.feed.post") { // Parse URI components - let post_did = parakeet_db::at_uri_util::extract_did(uri); - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(uri); + let post_did = parakeet_db::utils::at_uri::extract_did(uri); + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(uri); // Validate TID length before parsing if let (Some(post_did), Some(rkey_str)) = (post_did, post_rkey_str) { diff --git a/consumer/src/db/operations/community.rs b/consumer/src/db/operations/community.rs index bc104401..de99fe55 100644 --- a/consumer/src/db/operations/community.rs +++ b/consumer/src/db/operations/community.rs @@ -17,7 +17,7 @@ pub async fn bookmark_upsert( ) -> Result { // Parse the subject URI to extract (did, collection, rkey) let (post_did, collection, post_rkey_str) = - parakeet_db::at_uri_util::parse_at_uri(&rec.subject) + parakeet_db::utils::at_uri::parse_at_uri(&rec.subject) .ok_or_else(|| eyre::eyre!("Invalid AT URI: {}", rec.subject))?; // Validate it's a post URI diff --git a/consumer/src/db/operations/feed/feedgen.rs b/consumer/src/db/operations/feed/feedgen.rs index 2305a134..753bafbf 100644 --- a/consumer/src/db/operations/feed/feedgen.rs +++ b/consumer/src/db/operations/feed/feedgen.rs @@ -52,7 +52,7 @@ pub async fn feedgen_upsert( .await?; let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); let description_facets = rec .description_facets diff --git a/consumer/src/db/operations/feed/helpers.rs b/consumer/src/db/operations/feed/helpers.rs index 158a094c..112bcef1 100644 --- a/consumer/src/db/operations/feed/helpers.rs +++ b/consumer/src/db/operations/feed/helpers.rs @@ -99,13 +99,13 @@ pub(super) async fn get_feedgen_id( let cid = ipld_core::cid::Cid::try_from(cid_str) .wrap_err_with(|| format!("Invalid CID format: {}", cid_str))?; let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Extract owner DID and rkey from AT URI - let did = parakeet_db::at_uri_util::extract_did(at_uri) + let did = parakeet_db::utils::at_uri::extract_did(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; // Get/create actor_id for owner @@ -155,9 +155,9 @@ pub async fn ensure_list_natural_key( crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?; // Extract owner DID and rkey from AT URI - let did = parakeet_db::at_uri_util::extract_did(at_uri) + let did = parakeet_db::utils::at_uri::extract_did(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing DID in {}", at_uri))?; - let rkey = parakeet_db::at_uri_util::extract_rkey(at_uri) + let rkey = parakeet_db::utils::at_uri::extract_rkey(at_uri) .ok_or_else(|| eyre::eyre!("Invalid AT URI: missing rkey in {}", at_uri))?; // Get/create actor_id for owner diff --git a/consumer/src/db/operations/feed/like.rs b/consumer/src/db/operations/feed/like.rs index b188a8ac..b007a12d 100644 --- a/consumer/src/db/operations/feed/like.rs +++ b/consumer/src/db/operations/feed/like.rs @@ -87,11 +87,11 @@ pub async fn like_insert( let (subject_post_key, subject_feedgen_key, subject_labeler_actor_id, _subject_was_created): (Option<(i32, i64)>, Option<(i32, String)>, Option, bool) = match subject_collection { "app.bsky.feed.post" => { let subject_did = - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) })?; let subject_rkey = - parakeet_db::at_uri_util::extract_rkey(subject_uri).ok_or_else(|| { + parakeet_db::utils::at_uri::extract_rkey(subject_uri).ok_or_else(|| { eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri) })?; let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?; @@ -104,7 +104,7 @@ pub async fn like_insert( } "app.bsky.labeler.service" => { let subject_did = - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) })?; // Note: ensure_labeler_stub doesn't return was_created, so always enqueue labelers @@ -114,11 +114,11 @@ pub async fn like_insert( _ => { // Fallback: treat as post let subject_did = - parakeet_db::at_uri_util::extract_did(subject_uri).ok_or_else(|| { + parakeet_db::utils::at_uri::extract_did(subject_uri).ok_or_else(|| { eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri) })?; let subject_rkey = - parakeet_db::at_uri_util::extract_rkey(subject_uri).ok_or_else(|| { + parakeet_db::utils::at_uri::extract_rkey(subject_uri).ok_or_else(|| { eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri) })?; let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?; diff --git a/consumer/src/db/operations/feed/post.rs b/consumer/src/db/operations/feed/post.rs index d884ab5d..7c0cab3d 100644 --- a/consumer/src/db/operations/feed/post.rs +++ b/consumer/src/db/operations/feed/post.rs @@ -37,7 +37,7 @@ pub async fn post_insert( source: crate::database_writer::EventSource, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); let (_mentions, tags) = rec .facets @@ -83,9 +83,9 @@ pub async fn post_insert( // Track whether stubs were created so we only enqueue newly created stubs let (parent_post_key, _parent_was_created) = if let (Some(parent_uri), Some(parent_cid)) = (&parent_uri, &parent_cid) { - let parent_did = parakeet_db::at_uri_util::extract_did(parent_uri) + let parent_did = parakeet_db::utils::at_uri::extract_did(parent_uri) .ok_or_else(|| eyre::eyre!("Invalid parent URI: missing DID in {}", parent_uri))?; - let parent_rkey = parakeet_db::at_uri_util::extract_rkey(parent_uri) + let parent_rkey = parakeet_db::utils::at_uri::extract_rkey(parent_uri) .ok_or_else(|| eyre::eyre!("Invalid parent URI: missing rkey in {}", parent_uri))?; let (parent_actor_id, _, _) = get_actor_id(conn, parent_did).await?; let (post_key, was_created) = @@ -99,9 +99,9 @@ pub async fn post_insert( if let (Some(root_uri), Some(root_cid)) = (&root_uri, &root_cid) { // Only get root post natural key if it's different from parent if root_uri != parent_uri.as_ref().unwrap_or(&String::new()) { - let root_did = parakeet_db::at_uri_util::extract_did(root_uri) + let root_did = parakeet_db::utils::at_uri::extract_did(root_uri) .ok_or_else(|| eyre::eyre!("Invalid root URI: missing DID in {}", root_uri))?; - let root_rkey = parakeet_db::at_uri_util::extract_rkey(root_uri) + let root_rkey = parakeet_db::utils::at_uri::extract_rkey(root_uri) .ok_or_else(|| eyre::eyre!("Invalid root URI: missing rkey in {}", root_uri))?; let (root_actor_id, _, _) = get_actor_id(conn, root_did).await?; let (post_key, was_created) = @@ -148,9 +148,9 @@ pub async fn post_insert( // Get embedded post natural key let embed_uri = record.record.uri.as_str(); let embed_cid_str = record.record.cid.to_string(); - let embed_did = parakeet_db::at_uri_util::extract_did(embed_uri) + let embed_did = parakeet_db::utils::at_uri::extract_did(embed_uri) .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing DID in {}", embed_uri))?; - let embed_rkey = parakeet_db::at_uri_util::extract_rkey(embed_uri) + let embed_rkey = parakeet_db::utils::at_uri::extract_rkey(embed_uri) .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing rkey in {}", embed_uri))?; let (embed_actor_id, _, _) = get_actor_id(conn, embed_did).await?; let (embed_post_key, _) = get_post_id(conn, embed_actor_id, embed_rkey, &embed_cid_str).await?; @@ -173,9 +173,9 @@ pub async fn post_insert( // Process record part let embed_uri = rwm.record.uri.as_str(); let embed_cid_str = rwm.record.cid.to_string(); - let embed_did = parakeet_db::at_uri_util::extract_did(embed_uri) + let embed_did = parakeet_db::utils::at_uri::extract_did(embed_uri) .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing DID in {}", embed_uri))?; - let embed_rkey = parakeet_db::at_uri_util::extract_rkey(embed_uri) + let embed_rkey = parakeet_db::utils::at_uri::extract_rkey(embed_uri) .ok_or_else(|| eyre::eyre!("Invalid embed URI: missing rkey in {}", embed_uri))?; let (embed_actor_id, _, _) = get_actor_id(conn, embed_did).await?; let (embed_post_key, _) = get_post_id(conn, embed_actor_id, embed_rkey, &embed_cid_str).await?; diff --git a/consumer/src/db/operations/feed/postgate.rs b/consumer/src/db/operations/feed/postgate.rs index f0a06462..b755a6a3 100644 --- a/consumer/src/db/operations/feed/postgate.rs +++ b/consumer/src/db/operations/feed/postgate.rs @@ -35,9 +35,9 @@ pub async fn postgate_upsert( let rules: Vec = vec![]; // TODO: Extract rules from DisableRule when needed // Parse post URI to get post actor_id and rkey - let post_did = parakeet_db::at_uri_util::extract_did(rec.post.as_str()) + let post_did = parakeet_db::utils::at_uri::extract_did(rec.post.as_str()) .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: missing DID in {}", rec.post))?; - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(rec.post.as_str()) + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(rec.post.as_str()) .ok_or_else(|| eyre::eyre!("Invalid post URI in postgate: {}", rec.post))?; let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str) .wrap_err_with(|| format!("Invalid TID in postgate post URI: {}", post_rkey_str))?; diff --git a/consumer/src/db/operations/feed/repost.rs b/consumer/src/db/operations/feed/repost.rs index 4b3d02e3..4c5e91df 100644 --- a/consumer/src/db/operations/feed/repost.rs +++ b/consumer/src/db/operations/feed/repost.rs @@ -36,7 +36,7 @@ pub async fn repost_insert( _source: crate::database_writer::EventSource, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .expect("CID must be valid AT Protocol CID"); let subject_uri = &rec.subject.uri; @@ -50,9 +50,9 @@ pub async fn repost_insert( // This ensures the FK constraint is satisfied before we reach this point // Resolve post natural key (no stub creation with natural keys) - let subject_did = parakeet_db::at_uri_util::extract_did(subject_uri) + let subject_did = parakeet_db::utils::at_uri::extract_did(subject_uri) .ok_or_else(|| eyre::eyre!("Invalid subject URI: missing DID in {}", subject_uri))?; - let subject_rkey = parakeet_db::at_uri_util::extract_rkey(subject_uri) + let subject_rkey = parakeet_db::utils::at_uri::extract_rkey(subject_uri) .ok_or_else(|| eyre::eyre!("Invalid subject URI: missing rkey in {}", subject_uri))?; let (subject_actor_id, _, _) = get_actor_id(conn, subject_did).await?; let ((post_actor_id, post_rkey), _subject_was_created) = diff --git a/consumer/src/db/operations/feed/threadgate.rs b/consumer/src/db/operations/feed/threadgate.rs index f2da9d7f..932b38be 100644 --- a/consumer/src/db/operations/feed/threadgate.rs +++ b/consumer/src/db/operations/feed/threadgate.rs @@ -101,9 +101,9 @@ pub async fn threadgate_upsert( // but we extract it from the URI to be safe let post_uri = rec.post.as_ref() .ok_or_else(|| eyre::eyre!("Missing post URI in threadgate"))?; - let post_did = parakeet_db::at_uri_util::extract_did(post_uri.uri.as_str()) + let post_did = parakeet_db::utils::at_uri::extract_did(post_uri.uri.as_str()) .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: missing DID in {}", post_uri.uri))?; - let post_rkey_str = parakeet_db::at_uri_util::extract_rkey(post_uri.uri.as_str()) + let post_rkey_str = parakeet_db::utils::at_uri::extract_rkey(post_uri.uri.as_str()) .ok_or_else(|| eyre::eyre!("Invalid post URI in threadgate: {}", post_uri.uri))?; let post_rkey = parakeet_db::models::tid_to_i64(post_rkey_str) .wrap_err_with(|| format!("Invalid TID in threadgate post URI: {}", post_rkey_str))?; diff --git a/consumer/src/db/operations/graph.rs b/consumer/src/db/operations/graph.rs index a3245ee1..5858622b 100644 --- a/consumer/src/db/operations/graph.rs +++ b/consumer/src/db/operations/graph.rs @@ -185,7 +185,7 @@ pub async fn list_upsert( rec: AppBskyGraphList, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; let description_facets = rec .description_facets @@ -244,7 +244,7 @@ pub async fn list_block_insert( rec: AppBskyGraphListBlock, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Parse list URI to get natural keys (list_actor_id, list_rkey) @@ -323,7 +323,7 @@ pub async fn list_item_insert( rec: AppBskyGraphListItem, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Acquire advisory lock on record to prevent deadlocks @@ -375,7 +375,7 @@ pub async fn verification_insert( rec: AppBskyGraphVerification, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Acquire advisory lock on record to prevent deadlocks diff --git a/consumer/src/db/operations/labeler.rs b/consumer/src/db/operations/labeler.rs index 0321dd4d..ad81e423 100644 --- a/consumer/src/db/operations/labeler.rs +++ b/consumer/src/db/operations/labeler.rs @@ -29,7 +29,7 @@ pub async fn ensure_labeler_stub( let cid = ipld_core::cid::Cid::try_from(cid_str) .wrap_err_with(|| format!("Invalid CID format: {}", cid_str))?; let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Get/create actor_id (discard allowlist status and was_created, not needed for labelers) @@ -75,7 +75,7 @@ pub async fn labeler_upsert( crate::database_writer::locking::acquire_lock(conn, table_id, key_id).await?; let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; // Note: reason_types, subject_types, and subject_collections not available in jacquard types let reasons: Option> = None; diff --git a/consumer/src/db/operations/starter_pack.rs b/consumer/src/db/operations/starter_pack.rs index 475dd277..c7adb49a 100644 --- a/consumer/src/db/operations/starter_pack.rs +++ b/consumer/src/db/operations/starter_pack.rs @@ -12,7 +12,7 @@ pub async fn starter_pack_upsert( rec: AppBskyGraphStarterPack, ) -> Result { let cid_bytes = cid.to_bytes(); - let cid_digest = parakeet_db::cid_util::cid_to_digest(&cid_bytes) + let cid_digest = parakeet_db::utils::cid::cid_to_digest(&cid_bytes) .ok_or_eyre("CID must be valid AT Protocol CID")?; let record = serde_json::to_value(&rec).unwrap(); let description_facets = rec @@ -35,7 +35,7 @@ pub async fn starter_pack_upsert( for feed_ref in feeds { // Parse feed URI to extract (did, rkey) let (feed_did, collection, feed_rkey) = - parakeet_db::at_uri_util::parse_at_uri(feed_ref.uri.as_str()) + parakeet_db::utils::at_uri::parse_at_uri(feed_ref.uri.as_str()) .ok_or_else(|| eyre::eyre!("Invalid feed URI: {}", feed_ref.uri))?; // Validate it's a feedgen URI diff --git a/consumer/src/db/record_exists/mod.rs b/consumer/src/db/record_exists/mod.rs index 343778f3..f734109b 100644 --- a/consumer/src/db/record_exists/mod.rs +++ b/consumer/src/db/record_exists/mod.rs @@ -5,7 +5,7 @@ use deadpool_postgres::Object as PgObject; use eyre::Result; -use parakeet_db::tid_util::decode_tid; +use parakeet_db::utils::tid::decode_tid; pub mod queries; diff --git a/consumer/src/label_indexer/mod.rs b/consumer/src/label_indexer/mod.rs index ee52fe0d..d68785fe 100644 --- a/consumer/src/label_indexer/mod.rs +++ b/consumer/src/label_indexer/mod.rs @@ -87,7 +87,7 @@ async fn store_labels(conn: &mut tokio_postgres::Client, labels: &[AtpLabel]) -> for label in labels { // Parse AT URI to determine target - let Some((did, collection, rkey)) = parakeet_db::at_uri_util::parse_at_uri(&label.uri) else { + let Some((did, collection, rkey)) = parakeet_db::utils::at_uri::parse_at_uri(&label.uri) else { tracing::warn!("Invalid AT URI in label: {}", label.uri); continue; }; @@ -96,7 +96,7 @@ async fn store_labels(conn: &mut tokio_postgres::Client, labels: &[AtpLabel]) -> actor_labels.entry(did.to_string()).or_default().push(label); } else if collection == "app.bsky.feed.post" { // Convert TID string to INT8 - let Some(rkey_i64) = parakeet_db::tid_util::decode_tid(rkey) else { + let Some(rkey_i64) = parakeet_db::utils::tid::decode_tid(rkey) else { tracing::warn!("Invalid TID in label URI: {}", label.uri); continue; }; diff --git a/consumer/src/utils.rs b/consumer/src/utils.rs index f4fc89d7..32c2e57e 100644 --- a/consumer/src/utils.rs +++ b/consumer/src/utils.rs @@ -28,19 +28,19 @@ pub fn blob_ref(blob: Option<&Blob>) -> Option { /// Convert a Blob to CID bytes (32-byte digest) for database storage pub fn blob_to_cid_bytes(blob: Option<&Blob>) -> Option> { blob.and_then(|blob| { - parakeet_db::cid_util::blob_to_cid_bytes(Some(blob)) + parakeet_db::utils::cid::blob_to_cid_bytes(Some(blob)) }) } pub fn strongref_to_parts(strongref: Option<&StrongRef>) -> (Option, Option) { - parakeet_db::cid_util::strongref_to_parts(strongref) + parakeet_db::utils::cid::strongref_to_parts(strongref) } /// Convert a StrongRef to (URI, CID digest bytes) for pending linkage pub fn strongref_to_parts_with_digest( strongref: Option<&StrongRef>, ) -> (Option, Option>) { - parakeet_db::cid_util::strongref_to_parts_with_digest(strongref) + parakeet_db::utils::cid::strongref_to_parts_with_digest(strongref) } pub fn at_uri_is_by(uri: &str, did: &str) -> bool { diff --git a/consumer/tests/bulk_copy_queries_test.rs b/consumer/tests/bulk_copy_queries_test.rs index e38633c1..b3f19753 100644 --- a/consumer/tests/bulk_copy_queries_test.rs +++ b/consumer/tests/bulk_copy_queries_test.rs @@ -481,23 +481,23 @@ async fn test_insert_posts_with_video_embed_and_captions() -> eyre::Result<()> { ).await?; // Create VideoEmbed with all 3 captions to test serialization - let video_embed = parakeet_db::composite_types::VideoEmbed { + let video_embed = parakeet_db::composite::VideoEmbed { mime_type: parakeet_db::types::VideoMimeType::Mp4, cid: vec![1u8, 2, 3, 4, 5, 6, 7, 8], alt: Some("Test video".to_string()), width: Some(1920), height: Some(1080), - caption_1: Some(parakeet_db::composite_types::VideoCaption { + caption_1: Some(parakeet_db::composite::VideoCaption { lang: parakeet_db::types::LanguageCode::En, mime_type: parakeet_db::types::CaptionMimeType::Vtt, cid: vec![11u8, 12, 13, 14], }), - caption_2: Some(parakeet_db::composite_types::VideoCaption { + caption_2: Some(parakeet_db::composite::VideoCaption { lang: parakeet_db::types::LanguageCode::Es, mime_type: parakeet_db::types::CaptionMimeType::Vtt, cid: vec![21u8, 22, 23, 24], }), - caption_3: Some(parakeet_db::composite_types::VideoCaption { + caption_3: Some(parakeet_db::composite::VideoCaption { lang: parakeet_db::types::LanguageCode::Fr, mime_type: parakeet_db::types::CaptionMimeType::Vtt, cid: vec![31u8, 32, 33, 34], @@ -582,14 +582,14 @@ async fn test_insert_posts_with_image_embeds() -> eyre::Result<()> { tokens: vec![], ext_embed: None, video_embed: None, - image_1: Some(parakeet_db::composite_types::ImageEmbed { + image_1: Some(parakeet_db::composite::ImageEmbed { mime_type: parakeet_db::types::ImageMimeType::Jpeg, cid: vec![11u8, 12, 13, 14], alt: Some("Test image".to_string()), width: Some(1920), height: Some(1080), }), - image_2: Some(parakeet_db::composite_types::ImageEmbed { + image_2: Some(parakeet_db::composite::ImageEmbed { mime_type: parakeet_db::types::ImageMimeType::Png, cid: vec![21u8, 22, 23, 24], alt: None, @@ -643,7 +643,7 @@ async fn test_insert_posts_with_external_embed() -> eyre::Result<()> { embed_subtype: None, violates_threadgate: false, tokens: vec![], - ext_embed: Some(parakeet_db::composite_types::ExtEmbed { + ext_embed: Some(parakeet_db::composite::ExtEmbed { uri: "https://example.com".to_string(), title: Some("Example Site".to_string()), description: Some("Test description".to_string()), @@ -710,7 +710,7 @@ async fn test_insert_posts_with_facets() -> eyre::Result<()> { embedded_post_actor_id: None, embedded_post_rkey: None, record_detached: None, - facet_1: Some(parakeet_db::composite_types::FacetEmbed { + facet_1: Some(parakeet_db::composite::FacetEmbed { facet_type: parakeet_db::types::FacetType::Link, index_start: 0, index_end: 10, @@ -718,7 +718,7 @@ async fn test_insert_posts_with_facets() -> eyre::Result<()> { mention_actor_id: None, tag: None, }), - facet_2: Some(parakeet_db::composite_types::FacetEmbed { + facet_2: Some(parakeet_db::composite::FacetEmbed { facet_type: parakeet_db::types::FacetType::Mention, index_start: 11, index_end: 20, @@ -726,7 +726,7 @@ async fn test_insert_posts_with_facets() -> eyre::Result<()> { mention_actor_id: Some(mention_actor_id), tag: None, }), - facet_3: Some(parakeet_db::composite_types::FacetEmbed { + facet_3: Some(parakeet_db::composite::FacetEmbed { facet_type: parakeet_db::types::FacetType::Tag, index_start: 21, index_end: 30, diff --git a/consumer/tests/record_exists_queries_test.rs b/consumer/tests/record_exists_queries_test.rs index 748fbe16..384c45c9 100644 --- a/consumer/tests/record_exists_queries_test.rs +++ b/consumer/tests/record_exists_queries_test.rs @@ -6,7 +6,7 @@ mod common; use common::*; use consumer::db::record_exists::queries; -use parakeet_db::tid_util::decode_tid; +use parakeet_db::utils::tid::decode_tid; use eyre::WrapErr; // Valid test TID that can be decoded to i64 diff --git a/parakeet-db/src/composite_types.rs b/parakeet-db/src/composite/mod.rs similarity index 100% rename from parakeet-db/src/composite_types.rs rename to parakeet-db/src/composite/mod.rs diff --git a/parakeet-db/src/schema.rs b/parakeet-db/src/infrastructure/schema.rs similarity index 100% rename from parakeet-db/src/schema.rs rename to parakeet-db/src/infrastructure/schema.rs diff --git a/parakeet-db/src/types.rs b/parakeet-db/src/infrastructure/types/mod.rs similarity index 100% rename from parakeet-db/src/types.rs rename to parakeet-db/src/infrastructure/types/mod.rs diff --git a/parakeet-db/src/models.rs b/parakeet-db/src/models.rs deleted file mode 100644 index c5cd109d..00000000 --- a/parakeet-db/src/models.rs +++ /dev/null @@ -1,766 +0,0 @@ -// ============================================================================= -// MODELS.RS - Self-Contained Collection Models for Parakeet -// ============================================================================= -// -// This file contains Diesel model definitions that align with the -// self-contained collections schema where each table stores its own record metadata. -// -// KEY PRINCIPLES: -// -// 1. **Self-Contained Tables**: Each collection has actor_id, rkey, cid, created_at -// - No more record_id FK - data is in the collection table itself -// - Single JOIN to actors for DID/handle, no records table JOIN -// - 2-3x fewer JOINs in every query -// -// 2. **Type Safety via ENUMs**: -// - All protocol-defined enums use custom types (not String) -// - Diesel custom_types for type safety at query time -// - Compile-time validation of enum values -// -// 3. **Direct Foreign Keys**: -// - post_id: i64 (FK to posts, not records) -// - list_id: i64 (FK to lists, not records) -// - All FKs reference actual tables, not a central registry -// -// 4. **Discriminated Unions for Polymorphism**: -// - Likes use subject_type enum (post | feedgen | labeler) -// - Notifications use record_type enum (post | like | repost | follow | block) -// - 4-byte enum instead of table JOIN for type discrimination -// -// 5. **Optimized Binary Data**: -// - CIDs stored as Vec (32-byte digest, header stripped) -// - Signatures as Vec -// - No string overhead for binary data -// -// ============================================================================= - -use crate::composite_types::{Block, Bookmark, Follow, LabelerDef, Mute}; -use crate::tid_util::{decode_tid, encode_tid, TidError}; -use crate::types::*; -use chrono::prelude::*; -use diesel::prelude::*; -use serde::{Deserialize, Serialize}; - -// ============================================================================= -// HELPER TRAITS FOR TID RKEYS -// ============================================================================= - -/// Trait for models that have a TID-based rkey (stored as i64) -pub trait HasTidRkey { - /// Get the rkey as a TID string - fn rkey_str(&self) -> String; - - /// Get the rkey as i64 - fn rkey_i64(&self) -> i64; -} - -/// Helper to decode a TID string to i64 for use in queries/inserts -pub fn tid_to_i64(tid: &str) -> Result { - decode_tid(tid) -} - -/// Helper to encode an i64 rkey to TID string for use in AT URIs -pub fn i64_to_tid(value: i64) -> String { - encode_tid(value) -} - -// Macro to implement HasTidRkey trait for models -macro_rules! impl_tid_rkey { - ($model:ty) => { - impl HasTidRkey for $model { - fn rkey_str(&self) -> String { - encode_tid(self.rkey) - } - - fn rkey_i64(&self) -> i64 { - self.rkey - } - } - }; -} - -// Macro to implement created_at() method for TID-based models -// This derives the timestamp from the TID rkey, eliminating need for separate created_at column -macro_rules! impl_tid_created_at { - ($model:ty) => { - impl $model { - /// Get created_at timestamp derived from TID rkey - /// - /// TIDs encode a 53-bit microsecond timestamp in their upper bits. - /// This method extracts that timestamp, providing the original creation time - /// from the AT Protocol record without needing a separate database column. - pub fn created_at(&self) -> DateTime { - crate::tid_util::tid_to_datetime(self.rkey) - } - } - }; -} - -// ============================================================================= -// CORE IDENTITY -// ============================================================================= - -#[derive(Debug, Clone, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::actors)] -#[diesel(primary_key(id))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Actor { - pub id: i32, // PK: Internal actor ID - pub did: String, // Reverse DID lookup - pub handle: Option, // Handle (can change) - pub status: ActorStatus, // ENUM: active | takendown | suspended | deleted | deactivated - pub sync_state: ActorSyncState, // ENUM: synced | dirty | partial | processing - pub repo_rev: Option, // Repo revision - pub repo_cid: Option>, // Repo root CID (32 bytes) - pub last_indexed: Option>, - pub account_created_at: Option>, - // Profile fields (from app.bsky.actor.profile) - pub profile_cid: Option>, - pub profile_created_at: Option>, - pub profile_avatar_cid: Option>, - pub profile_banner_cid: Option>, - pub profile_display_name: Option, - pub profile_description: Option, - pub profile_pinned_post_rkey: Option, - pub profile_joined_sp_id: Option, - pub profile_pronouns: Option, - pub profile_website: Option, - // Note: profile_search_vector omitted from model (PostgreSQL tsvector, not loaded in Rust) - // Status fields (from app.bsky.actor.status) - pub status_cid: Option>, - pub status_created_at: Option>, - pub status_type: Option, - pub status_duration: Option, - pub status_embed_post_actor_id: Option, - pub status_embed_post_rkey: Option, - pub status_thumb_mime_type: Option, - pub status_thumb_cid: Option>, - // Chat declaration fields (from app.bsky.chat.declaration) - pub chat_allow_incoming: Option, - pub chat_created_at: Option>, - // Notification declaration fields (from app.bsky.notification.declaration) - pub notif_decl_allow_subscriptions: Option, - pub notif_decl_created_at: Option>, - // Notification state fields (from notification_state table) - pub notif_seen_at: Option>, - pub notif_unread_count: Option, - // Denormalized aggregate stats (from actor_aggregate_stats table) - // NULL = 0 for better compression (Gorilla stores NULLs as 1 bit in bitmap) - pub followers_count: Option, - pub following_count: Option, - pub posts_count: Option, - pub lists_count: Option, - pub feeds_count: Option, - pub starterpacks_count: Option, - // Labeler fields (from labelers table, denormalized - NULL for non-labelers) - pub labeler_cid: Option>, - pub labeler_created_at: Option>, - pub labeler_reasons: Option>>, - pub labeler_subject_types: Option>>, - pub labeler_subject_collections: Option>>, - pub labeler_status: Option, - pub labeler_like_count: Option, - pub labeler_defs: Option>>, - // Social graph arrays (from follows table, denormalized - bidirectional) - pub following: Option>>, // Who this actor follows - pub followers: Option>>, // Who follows this actor - // User preference arrays (from mutes, blocks, bookmarks tables, denormalized) - pub mutes: Option>>, // Muted actors - pub blocks: Option>>, // Blocked actors - pub bookmarks: Option>>, // Bookmarked posts - - // List moderation arrays (from thread_mutes, list_mutes, list_blocks tables, denormalized) - pub thread_mutes: Option>>, // Muted threads - pub list_mutes: Option>>, // Muted lists - pub list_blocks: Option>>, // List blocks - // Phase 6: RKey arrays for quick lookups - pub post_rkeys: Option>>, // All post rkeys owned by this actor - pub repost_rkeys: Option>>, // All repost rkeys owned by this actor - // Phase 7: Labels array (from labels table, denormalized) - pub labels: Option>>, // Labels applied to this actor -} - -// AllowlistEntry model removed - allowlist table dropped in favor of actors.sync_state -// -// Allowlist status is now determined by actors.sync_state: -// - sync_state IN ('synced', 'dirty', 'processing') = fully allowed -// - sync_state = 'partial' = not allowed (interacts with allowlisted users) -// -// The AllowlistEntry struct is kept in parakeet-db/src/allowlist.rs for API compatibility - -// ============================================================================= -// POSTS & CONTENT -// ============================================================================= - -#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::posts)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Post { - pub actor_id: i32, // PK part 1: FK to actors - pub rkey: i64, // PK part 2: TID as INT8 - pub cid: Vec, // 32-byte CID digest - pub content: Option>, // Zstd-compressed BYTEA (NULL for stubs) - pub langs: array_helpers::LanguageCodeArray, // ENUM array: ISO 639-1 codes (up to 3 per AT Protocol) - pub tags: array_helpers::TextArray, // Hashtags (GIN indexed) - pub embed_type: Option, // ENUM: images | video | external | record | record_with_media - pub embed_subtype: Option, // ENUM: For nested embeds (quote posts) - pub violates_threadgate: bool, - pub status: PostStatus, // ENUM: complete | stub | deleted | forbidden - pub tokens: Option, // Token array for search (GIN indexed, nullable) - // Denormalized embed fields (composite types) - pub ext_embed: Option, - pub video_embed: Option, - // Natural key references to other posts (hypertable → hypertable, NO FK constraints) - pub parent_post_actor_id: Option, // Parent post reference (for replies) - pub parent_post_rkey: Option, - pub root_post_actor_id: Option, // Root post reference (for threads) - pub root_post_rkey: Option, - pub embedded_post_actor_id: Option, // Quote post reference - pub embedded_post_rkey: Option, - pub record_detached: Option, - pub image_1: Option, - pub image_2: Option, - pub image_3: Option, - pub image_4: Option, - pub facet_1: Option, - pub facet_2: Option, - pub facet_3: Option, - pub facet_4: Option, - pub facet_5: Option, - pub facet_6: Option, - pub facet_7: Option, - pub facet_8: Option, - pub mentions: Option>>, // Array of actor_ids - // Embedded engagement arrays (array-only tracking, compute counts via array_length()) - // Likes: like_actor_ids[i] pairs with like_rkeys[i] - pub like_actor_ids: Option>>, // [actor1, actor2, ...] (who liked) - pub like_rkeys: Option>>, // [rkey1, rkey2, ...] (when they liked) - // Via repost tracking as JSONB: {"30": {"actor_id": 15, "rkey": 999}, ...} - // Key: liker's actor_id (as string), Value: {actor_id, rkey} of repost they came via - pub like_via_repost_data: Option, - // Replies: reply_actor_ids[i] pairs with reply_rkeys[i] - pub reply_actor_ids: Option>>, // [actor1, actor2, ...] (who replied) - pub reply_rkeys: Option>>, // [rkey1, rkey2, ...] (when they replied) - // Quotes: quote_actor_ids[i] pairs with quote_rkeys[i] - pub quote_actor_ids: Option>>, // [actor1, actor2, ...] (who quoted) - pub quote_rkeys: Option>>, // [rkey1, rkey2, ...] (when they quoted) - // Reposts: repost_actor_ids[i] pairs with repost_rkeys[i] - pub repost_actor_ids: Option>>, // [actor1, actor2, ...] (who reposted) - pub repost_rkeys: Option>>, // [rkey1, rkey2, ...] (when they reposted) - // Denormalized threadgate data (from threadgates table + threadgate_hidden_replies junction) - pub threadgate_allow: Option, // Rules (maxLength: 5) - pub threadgate_hidden_actor_ids: Option>>, // Hidden reply authors (maxLength: 300) - pub threadgate_hidden_rkeys: Option>>, // Hidden reply rkeys (parallel array) - // Denormalized postgate data (from postgates table + postgate_detached junction) - pub postgate_rules: Option, // Rules (maxLength: 5) - pub postgate_detached_actor_ids: Option>>, // Detached embed authors (maxLength: 50) - pub postgate_detached_rkeys: Option>>, // Detached embed rkeys (parallel array) - // Phase 7: Labels array (from labels table, denormalized) - pub labels: Option>>, // Labels applied to this post - // Phase 8: Engagement counts (maintained automatically by triggers) - pub like_count: i32, // Count of likes (maintained by update_post_counts trigger) - pub repost_count: i32, // Count of reposts (maintained by update_post_counts trigger) - pub reply_count: i32, // Count of replies (maintained by update_post_counts trigger) - pub quote_count: i32, // Count of quote posts (maintained by update_post_counts trigger) - // Note: created_at derived from TID rkey via created_at() method -} - -impl Post { - /// Get a specific like by index position - /// - /// Returns None if index is out of bounds or arrays are NULL - pub fn get_like(&self, idx: usize) -> Option { - let actor_ids = self.like_actor_ids.as_ref()?; - let rkeys = self.like_rkeys.as_ref()?; - - let actor_id = *actor_ids.get(idx)?.as_ref()?; - let rkey = *rkeys.get(idx)?.as_ref()?; - - // Extract via_repost from JSONB if it exists for this liker - let (via_repost_actor_id, via_repost_rkey) = self - .like_via_repost_data - .as_ref() - .and_then(|json| json.get(actor_id.to_string())) - .and_then(|data| { - let actor = data.get("actor_id")?.as_i64()? as i32; - let rkey = data.get("rkey")?.as_i64()?; - Some((Some(actor), Some(rkey))) - }) - .unwrap_or((None, None)); - - Some(PostLikeInfo { - actor_id, - rkey, - via_repost_actor_id, - via_repost_rkey, - }) - } - - /// Check if an actor has liked this post - /// - /// Returns Some(like_rkey) if the actor has liked, None otherwise - pub fn actor_liked(&self, actor_id: i32) -> Option { - let actor_ids = self.like_actor_ids.as_ref()?; - let rkeys = self.like_rkeys.as_ref()?; - - actor_ids - .iter() - .position(|id| id.as_ref() == Some(&actor_id)) - .and_then(|idx| rkeys.get(idx).and_then(|r| r.as_ref()).copied()) - } - - /// Get iterator over all likes - /// - /// Returns empty iterator if post has no likes - pub fn likes(&self) -> impl Iterator + '_ { - let count = self - .like_actor_ids - .as_ref() - .map(|v| v.len()) - .unwrap_or(0); - - (0..count).filter_map(move |i| self.get_like(i)) - } - - /// Get number of likes from array length (should match like_count) - pub fn likes_len(&self) -> usize { - self.like_actor_ids - .as_ref() - .map(|v| v.len()) - .unwrap_or(0) - } -} - -/// Represents a single like extracted from post arrays -#[derive(Debug, Clone)] -pub struct PostLikeInfo { - pub actor_id: i32, - pub rkey: i64, - pub via_repost_actor_id: Option, - pub via_repost_rkey: Option, -} - -impl PostLikeInfo { - /// Get created_at timestamp from the like's TID rkey - pub fn created_at(&self) -> DateTime { - crate::tid_util::tid_to_datetime(self.rkey) - } -} - -// Stats struct for API responses (replaces parakeet_index::PostStats) -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct PostStats { - pub likes: i32, // Keep as i32 for API compatibility - pub replies: i32, - pub reposts: i32, - pub quotes: i32, -} - -impl PostStats { - pub fn from_post(post: &Post) -> Self { - Self { - likes: post.like_count, - replies: post.reply_count, - reposts: post.repost_count, - quotes: post.quote_count, - } - } - - pub fn zero() -> Self { - Self { likes: 0, replies: 0, reposts: 0, quotes: 0 } - } -} - -// Profile stats struct for API responses (replaces parakeet_index::ProfileStats) -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct ProfileStats { - pub followers: i32, - pub following: i32, - pub posts: i32, - pub lists: i32, - pub feeds: i32, - pub starterpacks: i32, -} - -impl ProfileStats { - pub fn zero() -> Self { - Self { - followers: 0, - following: 0, - posts: 0, - lists: 0, - feeds: 0, - starterpacks: 0, - } - } -} - -// Post embed tables - reference posts directly - -#[derive(Debug, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::uris)] -#[diesel(primary_key(id))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Uri { - pub id: i32, // PK: Deduplication ID - pub uri: String, // Unique URI - pub created_at: DateTime, -} - -// Postgates - -// ============================================================================= -// SOCIAL INTERACTIONS -// ============================================================================= - -// Note: Feed Generator Likes table dropped - likes stored as arrays on feedgens table -// The like_actor_ids[] and like_rkeys[] arrays are maintained on feedgens -// This follows the same denormalization pattern as posts - -// Note: Labeler Likes table dropped - labeler data moved to actors table -// The labeler_like_count is maintained on actors.labeler_like_count -// Individual like records (labeler_likes table) were dropped for simplicity - -#[derive(Clone, Debug, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::reposts)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Repost { - pub actor_id: i32, // PK part 1: FK to actors - pub rkey: i64, // PK part 2: TID as INT8 - pub cid: Vec, // Real CID - needed for like.via_repost references - pub post_actor_id: i32, // Natural key reference to posts (regular → hypertable FK allowed) - NOT NULL - pub post_rkey: i64, // Natural key reference to posts - NOT NULL - pub via_repost_actor_id: Option, // Natural key self-reference (quote-repost-of-repost) - pub via_repost_rkey: Option, - pub status: RepostStatus, // ENUM: complete | stub - // Note: created_at derived from TID rkey via created_at() method -} - -// Follow model removed - follow relationships now stored as follow_record[] arrays on actors table -// Follow composite type is defined in composite_types.rs - -// Block, Mute, Bookmark models removed - user preferences now stored as arrays on actors table -// Composite types are defined in composite_types.rs - -// Profile, NotifDecl, ChatDecl, and Status structs removed - data now consolidated into Actor struct -// See actors table columns: profile_*, status_*, chat_*, notif_decl_*, notif_seen_at, notif_unread_count - -// ============================================================================= -// FEED GENERATORS -// ============================================================================= - -#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::feedgens)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct FeedGen { - pub actor_id: i32, // PK1: FK to actors - pub rkey: String, // PK2: Arbitrary user-chosen string (NOT TID, e.g. "music", "discover") - pub cid: Vec, // 32-byte CID digest - pub created_at: DateTime, // From AT Protocol record - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) - pub service_actor_id: i32, // FK to actors (denormalized for resolution) - pub content_mode: Option, // ENUM: contentModeUnspecified | contentModeVideo - pub name: Option, // NULL for stubs or deleted feedgens - pub description: Option, - pub description_facets: Option, - pub avatar_cid: Option>, // 32-byte CID - pub accepts_interactions: bool, - pub status: FeedgenStatus, // ENUM: complete | stub | deleted - pub like_count: i32, // Aggregated count of likes (maintained by database_writer) -} - -// ============================================================================= -// LISTS -// ============================================================================= - -#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::lists)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct List { - pub actor_id: i32, // PK1: FK to actors - pub rkey: String, // PK2: Can be TID or arbitrary string like "nfb", "bblock" - pub cid: Vec, // 32-byte CID digest - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) - pub list_type: Option, // ENUM: curatelist | modlist | referencelist (NULL for stubs) - pub name: Option, // NULL for stubs - pub description: Option, - pub description_facets: Option, - pub avatar_cid: Option>, // 32-byte CID - pub status: RecordStatus, // ENUM: complete | stub | deleted | forbidden - // Note: created_at derived from TID rkey if present, or epoch for non-TID rkeys -} - -#[derive(Debug, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::list_items)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct ListItem { - pub actor_id: i32, // PK: FK to actors - pub rkey: i64, // PK: TID as INT8 - pub cid: Vec, // 32-byte CID digest - pub subject_actor_id: i32, // FK to actors - pub list_owner_actor_id: i32, // FK to lists (natural key 1/2) - pub list_rkey: String, // FK to lists (natural key 2/2) - // Note: created_at derived from TID rkey via created_at() method -} - -// Note: ListBlock, ListMute, ThreadMute models removed -// List moderation data is now stored as arrays on actors table: -// - thread_mutes: thread_mute_record[] -// - list_mutes: list_mute_record[] -// - list_blocks: list_block_record[] -// Composite types are defined in composite_types.rs - -// ============================================================================= -// STARTER PACKS -// ============================================================================= - -#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::starterpacks)] -#[diesel(primary_key(actor_id, rkey))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct StarterPack { - pub actor_id: i32, // PK1: FK to actors - pub rkey: i64, // PK2: TID as INT8 - pub cid: Vec, // 32-byte CID digest - pub owner_actor_id: i32, // FK to actors (denormalized for filtering) - pub name: Option, // NULL for stubs - pub description: Option, - pub description_facets: Option, - pub list_actor_id: Option, // FK to lists (natural key 1/2, nullable) - pub list_rkey: Option, // FK to lists (natural key 2/2, nullable) - pub status: RecordStatus, // ENUM: complete | stub | deleted | forbidden - // Note: search_vector omitted from model - // Note: created_at derived from TID rkey via created_at() method -} - -#[derive(Clone, Debug, Queryable, Selectable)] -#[diesel(table_name = crate::schema::starterpack_feeds)] -#[diesel(primary_key(starterpack_id, position))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct StarterPackFeed { - pub starterpack_id: i64, // FK to starterpacks - pub position: i16, - pub feed_actor_id: i32, // FK to feedgens (natural key 1/2) - pub feed_rkey: String, // FK to feedgens (natural key 2/2) -} - -// ============================================================================= -// THREAD CONTROL -// ============================================================================= - -// Threadgate struct for denormalized use (no longer has a separate table) -// Used only for compatibility with EnrichedThreadgate in parakeet loader -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Threadgate { - pub actor_id: i32, // Post's actor_id (threadgate typically owned by post author) - pub rkey: i64, // Post's rkey (synthetic - threadgate has own rkey in reality) - pub cid: Vec, // Synthetic CID (deterministic based on post key) - pub allow: Option, // ENUM array: mention | following | list - pub post_actor_id: i32, // Same as actor_id (denormalized in post) - pub post_rkey: i64, // Same as rkey (denormalized in post) -} - -// ============================================================================= -// MODERATION & LABELS -// ============================================================================= - -// Note: Labeler and LabelerDef structs removed -// Labeler data is now stored directly on actors table with labeler_* columns -// LabelerDef is now a composite type in composite_types.rs, stored as labeler_defs array on actors - -#[derive(Clone, Debug)] -pub struct Label { - pub labeler_actor_id: i32, // PK: FK to actors - pub label: String, // PK: Label identifier - pub uri: String, // PK: Labeled URI - pub self_label: bool, - pub cid: Option>, // 32-byte CID - pub negated: bool, - pub expires: Option>, - pub sig: Option>, // Signature - pub created_at: DateTime, - pub labeler: String, // COMPUTED: Labeler DID (fetched via JOIN in loaders) -} - -#[derive(Clone, Debug, Serialize, Deserialize, Queryable, Selectable, Identifiable)] -#[diesel(table_name = crate::schema::verification)] -#[diesel(primary_key(id))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Verification { - pub id: i64, // PK: Surrogate key - pub actor_id: i32, // FK to actors - pub rkey: i64, // TID as INT8 - pub cid: Vec, // 32-byte CID digest - pub verifier_actor_id: i32, // FK to actors - pub subject_actor_id: i32, // FK to actors - pub handle: String, - pub display_name: String, - // Note: created_at derived from TID rkey via created_at() method -} - -// ChatDecl and Status structs removed - data now consolidated into Actor struct -// See actors table columns: chat_*, status_* - -// ============================================================================= -// HELPER TYPES FOR ARRAYS -// ============================================================================= - -// For diesel arrays with nullable elements that we want as Vec -pub mod array_helpers { - use diesel::deserialize::FromSql; - use diesel::pg::Pg; - use diesel::sql_types::{Array, Nullable, Text}; - use diesel::{deserialize, FromSqlRow}; - use serde::{Deserialize, Serialize}; - use std::ops::{Deref, DerefMut}; - - #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] - #[diesel(sql_type = Array>)] - pub struct TextArray(pub Vec); - - impl FromSql>, Pg> for TextArray { - fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result { - let vec_with_nulls = - > as FromSql>, Pg>>::from_sql(bytes)?; - Ok(TextArray(vec_with_nulls.into_iter().flatten().collect())) - } - } - - impl Deref for TextArray { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl DerefMut for TextArray { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - impl From for Vec { - fn from(v: TextArray) -> Vec { - v.0 - } - } - - // Macro to generate array helper types for ENUMs - macro_rules! enum_array { - ($name:ident, $enum_type:ty, $sql_type:ty) => { - #[derive(Clone, Debug, Default, Serialize, Deserialize, FromSqlRow)] - #[diesel(sql_type = Array>)] - pub struct $name(pub Vec<$enum_type>); - - impl FromSql>, Pg> for $name { - fn from_sql(bytes: diesel::pg::PgValue<'_>) -> deserialize::Result { - let vec_with_nulls = > as FromSql< - Array>, - Pg, - >>::from_sql(bytes)?; - Ok($name(vec_with_nulls.into_iter().flatten().collect())) - } - } - - impl Deref for $name { - type Target = Vec<$enum_type>; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl DerefMut for $name { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - - impl From<$name> for Vec<$enum_type> { - fn from(v: $name) -> Vec<$enum_type> { - v.0 - } - } - }; - } - - // ENUM array types - enum_array!( - PostgateRuleArray, - crate::types::PostgateRule, - crate::schema::sql_types::PostgateRule - ); - enum_array!( - ThreadgateRuleArray, - crate::types::ThreadgateRule, - crate::schema::sql_types::ThreadgateRule - ); - enum_array!( - ReasonTypeArray, - crate::types::ReasonType, - crate::schema::sql_types::ReasonType - ); - enum_array!( - SubjectTypeArray, - crate::types::SubjectType, - crate::schema::sql_types::SubjectType - ); - enum_array!( - LanguageCodeArray, - crate::types::LanguageCode, - crate::schema::sql_types::LanguageCode - ); -} - -// ============================================================================= -// TID RKEY IMPLEMENTATIONS -// ============================================================================= -// Implement HasTidRkey for all models with TID-based rkeys - -impl_tid_rkey!(Post); -// impl_tid_rkey!(FeedgenLike); // Removed - feedgen_likes table dropped, now like_actor_ids[]/like_rkeys[] arrays -// impl_tid_rkey!(LabelerLike); // Removed - labeler_likes table dropped -impl_tid_rkey!(Repost); -// impl_tid_rkey!(Follow); // Removed - follows table dropped, now follow_record[] arrays -impl_tid_rkey!(Block); -impl_tid_rkey!(Bookmark); -// NOTE: FeedGen and List intentionally excluded - use arbitrary String rkeys, not TIDs -impl_tid_rkey!(ListItem); -// impl_tid_rkey!(ListBlock); // Removed - list_blocks table dropped, now list_block_record[] arrays -impl_tid_rkey!(StarterPack); -impl_tid_rkey!(Verification); - -// ============================================================================= -// TID TIMESTAMP IMPLEMENTATIONS -// ============================================================================= -// Implement created_at() method for models where we derive timestamp from TID -// This eliminates the need for a separate created_at column in the database - -impl_tid_created_at!(Post); -// impl_tid_created_at!(FeedgenLike); // Removed - feedgen_likes table dropped, now like_actor_ids[]/like_rkeys[] arrays -// impl_tid_created_at!(LabelerLike); // Removed - labeler_likes table dropped -impl_tid_created_at!(Repost); -// impl_tid_created_at!(Follow); // Removed - follows table dropped, now follow_record[] arrays -impl_tid_created_at!(Block); -impl_tid_created_at!(Bookmark); -impl_tid_created_at!(ListItem); -// impl_tid_created_at!(ListBlock); // Removed - list_blocks table dropped, now list_block_record[] arrays -// NOTE: List uses String rkey and has custom created_at() implementation -impl_tid_created_at!(StarterPack); -impl_tid_created_at!(Verification); - -// Custom created_at() implementation for List (String rkey can be TID or arbitrary) -impl List { - /// Get created_at timestamp derived from rkey if it's a TID, or epoch for non-TID rkeys - pub fn created_at(&self) -> DateTime { - crate::models::tid_to_i64(&self.rkey) - .map(crate::tid_util::tid_to_datetime) - .unwrap_or_else(|_| DateTime::::UNIX_EPOCH) - } -} diff --git a/parakeet-db/src/at_uri_util.rs b/parakeet-db/src/utils/at_uri.rs similarity index 100% rename from parakeet-db/src/at_uri_util.rs rename to parakeet-db/src/utils/at_uri.rs diff --git a/parakeet-db/src/cid_util.rs b/parakeet-db/src/utils/cid.rs similarity index 100% rename from parakeet-db/src/cid_util.rs rename to parakeet-db/src/utils/cid.rs diff --git a/parakeet-db/src/compression.rs b/parakeet-db/src/utils/compression.rs similarity index 100% rename from parakeet-db/src/compression.rs rename to parakeet-db/src/utils/compression.rs diff --git a/parakeet-db/src/tid_util.rs b/parakeet-db/src/utils/tid.rs similarity index 100% rename from parakeet-db/src/tid_util.rs rename to parakeet-db/src/utils/tid.rs diff --git a/parakeet/src/common/cache_listener.rs b/parakeet/src/common/cache_listener.rs index ff4dfd41..2e912879 100644 --- a/parakeet/src/common/cache_listener.rs +++ b/parakeet/src/common/cache_listener.rs @@ -171,7 +171,7 @@ async fn handle_cache_invalidation(state: &GlobalState, cache_key: &str) { if let Some((actor_id_str, rkey_str)) = rest.split_once(':') { if let Ok(actor_id) = actor_id_str.parse::() { // Parse TID rkey - if let Ok(rkey) = parakeet_db::tid_util::decode_tid(rkey_str) { + if let Ok(rkey) = parakeet_db::utils::tid::decode_tid(rkey_str) { // Invalidate StarterpackEntity cache use crate::entities::core::starterpack::StarterpackKey; state.starterpack_entity.invalidate(vec![StarterpackKey { diff --git a/parakeet/src/common/helpers.rs b/parakeet/src/common/helpers.rs index 62fa544f..cfa5b00a 100644 --- a/parakeet/src/common/helpers.rs +++ b/parakeet/src/common/helpers.rs @@ -156,7 +156,7 @@ use serde::Deserialize; /// This matches the official Bluesky API which uses base32-encoded TIDs /// like "3m47vaoniuy2d" for pagination cursors. pub fn tid_cursor(cursor: Option<&String>) -> Option { - cursor.and_then(|v| parakeet_db::tid_util::decode_tid(v).ok()) + cursor.and_then(|v| parakeet_db::utils::tid::decode_tid(v).ok()) } /// Parses a datetime cursor string (ISO 8601 format) into a DateTime diff --git a/parakeet/src/entities/converters/post.rs b/parakeet/src/entities/converters/post.rs index 683ceae5..f09ccbb1 100644 --- a/parakeet/src/entities/converters/post.rs +++ b/parakeet/src/entities/converters/post.rs @@ -26,7 +26,7 @@ impl PostEntity { .quote_count(Some(post_data.post.quote_count as i64)) .bookmark_count(Some(0)) .indexed_at(Datetime::new( - ¶keet_db::tid_util::tid_to_datetime(post_data.post.rkey) + ¶keet_db::utils::tid::tid_to_datetime(post_data.post.rkey) .to_rfc3339_opts(chrono::SecondsFormat::Millis, true) ).unwrap()) .build() @@ -45,8 +45,8 @@ impl PostEntity { // Build the post URI and CID let uri = format!("at://{}/app.bsky.feed.post/{}", author_profile.did.as_str(), - parakeet_db::tid_util::encode_tid(post_data.post.rkey)); - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&post_data.post.cid).unwrap_or_default(); + parakeet_db::utils::tid::encode_tid(post_data.post.rkey)); + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&post_data.post.cid).unwrap_or_default(); Some(self.post_to_post_view(&post_data, author_profile, &uri, &cid)) } diff --git a/parakeet/src/entities/converters/profile.rs b/parakeet/src/entities/converters/profile.rs index d3430c8f..64c299c4 100644 --- a/parakeet/src/entities/converters/profile.rs +++ b/parakeet/src/entities/converters/profile.rs @@ -3,7 +3,7 @@ /// This replaces the complex hydration layer with simple, direct conversions use crate::entities::core::{ProfileEntity, PostEntity, StarterpackEntity}; -use parakeet_db::models::Actor; +use parakeet_db::domain::Actor; use jacquard_api::app_bsky::actor::{ ProfileView, ProfileViewDetailed, ProfileViewBasic, ProfileAssociated, ProfileAssociatedChat, @@ -22,7 +22,7 @@ pub fn actor_to_profile_view(actor: &Actor) -> ProfileView<'static> { // Keep avatar_url alive for the entire function scope let avatar_url = actor.profile_avatar_cid.as_ref() - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); let mut builder = ProfileView::new() @@ -72,7 +72,7 @@ impl ProfileEntity { .and_then(|follows| { follows.iter().flatten().find(|f| f.subject_actor_id == actor.id) .map(|f| format!("at://{}/app.bsky.graph.follow/{}", viewer_did, - parakeet_db::tid_util::encode_tid(f.rkey))) + parakeet_db::utils::tid::encode_tid(f.rkey))) }); // Check if this actor follows viewer @@ -81,7 +81,7 @@ impl ProfileEntity { .and_then(|follows| { follows.iter().flatten().find(|f| f.subject_actor_id == viewer_actor_id) .map(|f| format!("at://{}/app.bsky.graph.follow/{}", actor.did, - parakeet_db::tid_util::encode_tid(f.rkey))) + parakeet_db::utils::tid::encode_tid(f.rkey))) }); // Check if viewer has muted this actor @@ -106,7 +106,7 @@ impl ProfileEntity { .and_then(|blocks| { blocks.iter().flatten().find(|b| b.subject_actor_id == actor.id) .map(|b| format!("at://{}/app.bsky.graph.block/{}", viewer_did, - parakeet_db::tid_util::encode_tid(b.rkey))) + parakeet_db::utils::tid::encode_tid(b.rkey))) }); Some(ViewerState { @@ -130,11 +130,11 @@ impl ProfileEntity { // Keep URLs alive for the entire function scope let avatar_url = actor.profile_avatar_cid.as_ref() - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); let banner_url = actor.profile_banner_cid.as_ref() - .and_then(|banner_cid| parakeet_db::cid_util::digest_to_blob_cid_string(banner_cid)) + .and_then(|banner_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(banner_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/banner/plain/{}/{}@jpeg", actor.did, cid_str)); let mut builder = ProfileViewDetailed::new() @@ -221,11 +221,11 @@ impl ProfileEntity { // Keep URLs alive for the entire function scope let avatar_url = actor.profile_avatar_cid.as_ref() - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); let banner_url = actor.profile_banner_cid.as_ref() - .and_then(|banner_cid| parakeet_db::cid_util::digest_to_blob_cid_string(banner_cid)) + .and_then(|banner_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(banner_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/banner/plain/{}/{}@jpeg", actor.did, cid_str)); let mut builder = ProfileViewDetailed::new() @@ -334,7 +334,7 @@ impl ProfileEntity { // Keep avatar_url alive for the entire function scope let avatar_url = actor.profile_avatar_cid.as_ref() - .and_then(|avatar_cid| parakeet_db::cid_util::digest_to_blob_cid_string(avatar_cid)) + .and_then(|avatar_cid| parakeet_db::utils::cid::digest_to_blob_cid_string(avatar_cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/avatar/plain/{}/{}@jpeg", actor.did, cid_str)); let mut builder = ProfileViewBasic::new() diff --git a/parakeet/src/entities/core/feedgen.rs b/parakeet/src/entities/core/feedgen.rs index b3a43eb9..0b3f7ffa 100644 --- a/parakeet/src/entities/core/feedgen.rs +++ b/parakeet/src/entities/core/feedgen.rs @@ -282,11 +282,11 @@ impl FeedGeneratorEntity { }; // Convert CID - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.cid); + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.cid); // Build avatar URL if present let avatar = data.avatar_cid.as_ref().and_then(|cid_bytes| { - parakeet_db::cid_util::digest_to_blob_cid_string(cid_bytes) + parakeet_db::utils::cid::digest_to_blob_cid_string(cid_bytes) .map(|avatar_cid| format!("{}/avatar/{}", self.cdn_base, avatar_cid)) }); diff --git a/parakeet/src/entities/core/list.rs b/parakeet/src/entities/core/list.rs index 75531ced..4efc8bb0 100644 --- a/parakeet/src/entities/core/list.rs +++ b/parakeet/src/entities/core/list.rs @@ -282,11 +282,11 @@ impl ListEntity { let creator_view = crate::entities::converters::profile::actor_to_profile_view(&creator); // Convert CID - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(data.cid()); + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(data.cid()); // Build avatar URL if present let avatar = data.avatar_cid().and_then(|cid_bytes| { - parakeet_db::cid_util::digest_to_blob_cid_string(cid_bytes) + parakeet_db::utils::cid::digest_to_blob_cid_string(cid_bytes) .map(|avatar_cid| format!("{}/avatar/{}", self.cdn_base, avatar_cid)) }); @@ -404,8 +404,8 @@ impl ListEntity { let mut processed = Vec::new(); for row in results { // Decode TID from string to get timestamp - if let Ok(tid) = parakeet_db::tid_util::decode_tid(&row.rkey) { - let created_at = parakeet_db::tid_util::tid_to_datetime(tid); + if let Ok(tid) = parakeet_db::utils::tid::decode_tid(&row.rkey) { + let created_at = parakeet_db::utils::tid::tid_to_datetime(tid); // Apply cursor filter in Rust if let Some(cursor_ts) = cursor { @@ -476,7 +476,7 @@ impl ListEntity { let mut processed = Vec::new(); for row in results { // Convert TID to timestamp - let created_at = parakeet_db::tid_util::tid_to_datetime(row.rkey); + let created_at = parakeet_db::utils::tid::tid_to_datetime(row.rkey); // Apply cursor filter in Rust if let Some(cursor_ts) = cursor { diff --git a/parakeet/src/entities/core/notification.rs b/parakeet/src/entities/core/notification.rs index fd3b1a7e..c2814f15 100644 --- a/parakeet/src/entities/core/notification.rs +++ b/parakeet/src/entities/core/notification.rs @@ -284,7 +284,7 @@ impl NotificationEntity { let mut map = std::collections::HashMap::new(); for r in results { - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.subject_rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.subject_rkey); let subject_uri = format!("at://{}/app.bsky.feed.post/{}", r.subject_did, encoded_rkey); map.insert((r.actor_id, r.rkey), (subject_uri, r.created_at)); } @@ -340,7 +340,7 @@ impl NotificationEntity { let mut map = std::collections::HashMap::new(); for r in results { - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.post_rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.post_rkey); let post_uri = format!("at://{}/app.bsky.feed.post/{}", r.post_did, encoded_rkey); map.insert((r.actor_id, r.rkey), (post_uri, r.created_at)); } diff --git a/parakeet/src/entities/core/post.rs b/parakeet/src/entities/core/post.rs index e61af181..5a62416b 100644 --- a/parakeet/src/entities/core/post.rs +++ b/parakeet/src/entities/core/post.rs @@ -10,7 +10,7 @@ use jacquard_api::com_atproto::repo::strong_ref::StrongRef; use jacquard_common::types::string::{AtUri, Cid, Datetime}; use jacquard_common::types::value::Data; use jacquard_common::IntoStatic; -use parakeet_db::models::{Post, Actor}; +use parakeet_db::domain::{Post, Actor}; use std::sync::Arc; use std::collections::HashMap; use std::time::Duration; @@ -69,13 +69,13 @@ impl PostData { /// Convert this post data to a StrongRef (AT URI + CID) pub fn to_strong_ref(&self) -> Option> { // Convert the CID bytes to the proper string format - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&self.post.cid)?; + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&self.post.cid)?; // Build the AT URI let uri = format!( "at://{}/app.bsky.feed.post/{}", self.author.did, - parakeet_db::tid_util::encode_tid(self.post.rkey) + parakeet_db::utils::tid::encode_tid(self.post.rkey) ); StrongRef::new_from_str(uri, &cid_str).ok().map(|sr| sr.into_static()) @@ -121,7 +121,7 @@ impl PostEntity { let uri = format!( "at://{}/app.bsky.feed.post/{}", post_data.author.did, - parakeet_db::tid_util::encode_tid(post_data.post.rkey) + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) ); self.uri_to_key.invalidate(&uri).await; } @@ -152,7 +152,7 @@ impl PostEntity { let tid_str = parts[2]; // Decode the TID to get rkey - let rkey = parakeet_db::tid_util::decode_tid(tid_str) + let rkey = parakeet_db::utils::tid::decode_tid(tid_str) .map_err(|_| eyre::eyre!("Invalid TID in URI"))?; // Get actor_id from ProfileEntity (uses its cache) @@ -194,7 +194,7 @@ impl PostEntity { let uri = format!( "at://{}/app.bsky.feed.post/{}", post_data.author.did, - parakeet_db::tid_util::encode_tid(post_data.post.rkey) + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) ); self.uri_to_key.insert(uri, key).await; self.post_cache.insert(key, post_data.clone()).await; @@ -252,7 +252,7 @@ impl PostEntity { let uri = format!( "at://{}/app.bsky.feed.post/{}", post_data.author.did, - parakeet_db::tid_util::encode_tid(post_data.post.rkey) + parakeet_db::utils::tid::encode_tid(post_data.post.rkey) ); self.uri_to_key.insert(uri, key).await; self.post_cache.insert(key, post_data.clone()).await; @@ -513,10 +513,10 @@ impl PostEntity { let uri = format!( "at://{}/app.bsky.feed.post/{}", data.author.did, - parakeet_db::tid_util::encode_tid(data.post.rkey) + parakeet_db::utils::tid::encode_tid(data.post.rkey) ); - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.post.cid) + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.post.cid) .unwrap_or_else(|| "bafyreiunknown".to_string()); // Extract text from content if available @@ -530,7 +530,7 @@ impl PostEntity { }; // Get created_at from TID rkey - let created_at = parakeet_db::tid_util::tid_to_datetime(data.post.rkey); + let created_at = parakeet_db::utils::tid::tid_to_datetime(data.post.rkey); // Compute viewer state if viewer is provided let viewer = if let Some(viewer_id) = viewer_actor_id { @@ -575,7 +575,7 @@ impl PostEntity { // Build viewer state with like/repost URIs if applicable let like_uri = if like { // Generate proper like URI with TID - let like_tid = parakeet_db::tid_util::timestamp_to_tid(chrono::Utc::now()); + let like_tid = parakeet_db::utils::tid::timestamp_to_tid(chrono::Utc::now()); let viewer_did = viewer_actor.as_ref().map(|a| a.did.clone()).unwrap_or_else(|| "unknown".to_string()); Some(format!("at://{}/app.bsky.feed.like/{}", viewer_did, like_tid)) } else { @@ -584,7 +584,7 @@ impl PostEntity { let repost_uri = if repost { // Generate proper repost URI with TID - let repost_tid = parakeet_db::tid_util::timestamp_to_tid(chrono::Utc::now()); + let repost_tid = parakeet_db::utils::tid::timestamp_to_tid(chrono::Utc::now()); let viewer_did = viewer_actor.as_ref().map(|a| a.did.clone()).unwrap_or_else(|| "unknown".to_string()); Some(format!("at://{}/app.bsky.feed.repost/{}", viewer_did, repost_tid)) } else { @@ -659,7 +659,7 @@ impl PostEntity { // Check each image field for img_opt in [&data.post.image_1, &data.post.image_2, &data.post.image_3, &data.post.image_4] { if let Some(img) = img_opt { - if let Some(cid_str) = parakeet_db::cid_util::digest_to_blob_cid_string(&img.cid) { + if let Some(cid_str) = parakeet_db::utils::cid::digest_to_blob_cid_string(&img.cid) { images.push(ImageView { thumb: format!("https://cdn.bsky.social/img/feed_thumbnail/plain/{}/{}@jpeg", data.author.did, cid_str), @@ -694,7 +694,7 @@ impl PostEntity { title: ext.title.clone().unwrap_or_default(), description: ext.description.clone().unwrap_or_default(), thumb: ext.thumb_cid.as_ref() - .and_then(|cid| parakeet_db::cid_util::digest_to_blob_cid_string(cid)) + .and_then(|cid| parakeet_db::utils::cid::digest_to_blob_cid_string(cid)) .map(|cid_str| format!("https://cdn.bsky.social/img/feed_thumbnail/plain/{}/{}@jpeg", data.author.did, cid_str)), } @@ -751,7 +751,7 @@ impl PostEntity { let parent_uri = format!( "at://{}/app.bsky.feed.post/{}", parent_did, - parakeet_db::tid_util::encode_tid(parent_rkey) + parakeet_db::utils::tid::encode_tid(parent_rkey) ); ReplyRefPost::NotFound { uri: parent_uri, @@ -793,7 +793,7 @@ impl PostEntity { let root_uri = format!( "at://{}/app.bsky.feed.post/{}", root_did, - parakeet_db::tid_util::encode_tid(root_rkey) + parakeet_db::utils::tid::encode_tid(root_rkey) ); ReplyRefPost::NotFound { uri: root_uri, @@ -803,7 +803,7 @@ impl PostEntity { // If we can't build root URI, create NotFound with placeholder ReplyRefPost::NotFound { uri: format!("at://unknown/app.bsky.feed.post/{}", - parakeet_db::tid_util::encode_tid(root_rkey)), + parakeet_db::utils::tid::encode_tid(root_rkey)), not_found: true, } } @@ -864,7 +864,7 @@ impl PostEntity { for (like_actor_id, like_rkey) in actor_ids.into_iter().zip(rkeys.into_iter()) { // Skip None values if let (Some(actor_id), Some(rkey)) = (like_actor_id, like_rkey) { - let timestamp = parakeet_db::tid_util::tid_to_datetime(rkey); + let timestamp = parakeet_db::utils::tid::tid_to_datetime(rkey); // Apply cursor filter if let Some(cursor_ts) = cursor { @@ -1082,7 +1082,7 @@ impl PostEntity { .optional()?; Ok(result.map(|(post_actor_id, post_rkey)| { - let timestamp = parakeet_db::tid_util::tid_to_datetime(rkey); + let timestamp = parakeet_db::utils::tid::tid_to_datetime(rkey); (post_actor_id, post_rkey, timestamp) })) } @@ -1367,7 +1367,7 @@ impl PostEntity { Ok(results .into_iter() .map(|r| { - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.rkey); let uri = format!("at://{}/app.bsky.feed.post/{}", r.did, encoded_rkey); ((r.actor_id, r.rkey), uri) }) diff --git a/parakeet/src/entities/core/profile.rs b/parakeet/src/entities/core/profile.rs index 6c0da64c..d6d80fbc 100644 --- a/parakeet/src/entities/core/profile.rs +++ b/parakeet/src/entities/core/profile.rs @@ -429,14 +429,14 @@ impl ProfileEntity { actor_id: i32, cursor_ts: Option<&chrono::DateTime>, limit: u8, - ) -> Result> { + ) -> Result> { // If the actor doesn't exist, they have no blocks let actor = match self.get_profile_by_id(actor_id).await { Ok(a) => a, Err(_) => return Ok(Vec::new()), }; - let blocks: Vec = actor.blocks + let blocks: Vec = actor.blocks .as_ref() .map(|blocks| blocks.iter().flatten().cloned().collect()) .unwrap_or_default(); @@ -445,7 +445,7 @@ impl ProfileEntity { let filtered: Vec<_> = if let Some(cursor) = cursor_ts { blocks.into_iter() .filter(|b| { - let dt = parakeet_db::tid_util::tid_to_datetime(b.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(b.rkey); dt < *cursor }) .take(limit as usize + 1) @@ -484,14 +484,14 @@ impl ProfileEntity { actor_id: i32, cursor_ts: Option<&chrono::DateTime>, limit: u8, - ) -> Result> { + ) -> Result> { // If the actor doesn't exist, they have no mutes let actor = match self.get_profile_by_id(actor_id).await { Ok(a) => a, Err(_) => return Ok(Vec::new()), }; - let mutes: Vec = actor.mutes + let mutes: Vec = actor.mutes .as_ref() .map(|mutes| mutes.iter().flatten().cloned().collect()) .unwrap_or_default(); @@ -515,14 +515,14 @@ impl ProfileEntity { actor_id: i32, cursor_ts: Option<&chrono::DateTime>, limit: u8, - ) -> Result> { + ) -> Result> { // If the actor doesn't exist, they have no muted lists let actor = match self.get_profile_by_id(actor_id).await { Ok(a) => a, Err(_) => return Ok(Vec::new()), }; - let list_mutes: Vec = actor.list_mutes + let list_mutes: Vec = actor.list_mutes .as_ref() .map(|mutes| mutes.iter().flatten().cloned().collect()) .unwrap_or_default(); @@ -580,14 +580,14 @@ impl ProfileEntity { actor_id: i32, cursor_ts: Option<&chrono::DateTime>, limit: u8, - ) -> Result> { + ) -> Result> { // If the actor doesn't exist, they follow nobody let actor = match self.get_profile_by_id(actor_id).await { Ok(a) => a, Err(_) => return Ok(Vec::new()), }; - let follows: Vec = actor.following + let follows: Vec = actor.following .as_ref() .map(|follows| follows.iter().flatten().cloned().collect()) .unwrap_or_default(); @@ -596,7 +596,7 @@ impl ProfileEntity { let filtered: Vec<_> = if let Some(cursor) = cursor_ts { follows.into_iter() .filter(|f| { - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); dt < *cursor }) .take(limit as usize + 1) @@ -849,7 +849,7 @@ impl ProfileEntity { // If we have a cursor, filter out posts created before it if let Some(cursor_ts) = cursor { posts_liked.retain(|(_, rkey)| { - let timestamp = parakeet_db::tid_util::tid_to_datetime(*rkey); + let timestamp = parakeet_db::utils::tid::tid_to_datetime(*rkey); timestamp > *cursor_ts }); } diff --git a/parakeet/src/entities/core/starterpack.rs b/parakeet/src/entities/core/starterpack.rs index 817ce81a..14f79fe2 100644 --- a/parakeet/src/entities/core/starterpack.rs +++ b/parakeet/src/entities/core/starterpack.rs @@ -203,7 +203,7 @@ impl StarterpackEntity { let actor_id = self.profile_entity.resolve_identifier(did).await.ok()?; // Parse TID rkey - let rkey = parakeet_db::tid_util::decode_tid(rkey_str).ok()?; + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str).ok()?; Some((actor_id, rkey)) } @@ -253,7 +253,7 @@ impl StarterpackEntity { .await .unwrap_or_else(|_| view.creator.did.to_string()); - let rkey_str = parakeet_db::tid_util::encode_tid(keys.iter() + let rkey_str = parakeet_db::utils::tid::encode_tid(keys.iter() .find(|(_, k)| k.actor_id == actor_id) .map(|(_, k)| k.rkey) .unwrap_or(0)); @@ -280,7 +280,7 @@ impl StarterpackEntity { let creator_view = self.profile_entity.actor_to_profile_view_basic(&creator, None).await; // Convert CID - let cid = parakeet_db::cid_util::digest_to_blob_cid_string(&data.cid); + let cid = parakeet_db::utils::cid::digest_to_blob_cid_string(&data.cid); // Parse description facets let description_facets: Option> = data.description_facets.and_then(|v| { @@ -288,7 +288,7 @@ impl StarterpackEntity { }); // Build the URI - let rkey_str = parakeet_db::tid_util::encode_tid(data.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(data.rkey); let uri = format!("at://{}/app.bsky.graph.starterpack/{}", creator.did, rkey_str); // Get indexed_at - use current time for now @@ -314,7 +314,7 @@ impl StarterpackEntity { let (list_item_count, joined_week_count, joined_all_time_count) = if let (Some(list_actor_id), Some(list_rkey_str)) = (data.list_actor_id, &data.list_rkey) { // Decode the base32 rkey to i64 - let list_rkey = parakeet_db::tid_util::decode_tid(list_rkey_str).unwrap_or(0); + let list_rkey = parakeet_db::utils::tid::decode_tid(list_rkey_str).unwrap_or(0); self.get_list_counts(list_actor_id, list_rkey).await.unwrap_or((0, 0, 0)) } else { (0, 0, 0) @@ -578,7 +578,7 @@ impl StarterpackEntity { Ok(rows.into_iter() .map(|r| { - let encoded_rkey = parakeet_db::tid_util::encode_tid(r.rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(r.rkey); let at_uri = format!("at://{}/app.bsky.graph.starterpack/{}", r.did, encoded_rkey); (at_uri, r.owner, r.name, r.description) }) @@ -732,7 +732,7 @@ impl StarterpackEntity { if let Ok(item_owner_did) = self.profile_entity.get_did_by_id(item_actor_id).await { let item_uri = format!("at://{}/app.bsky.graph.listitem/{}", item_owner_did, - parakeet_db::tid_util::encode_tid(item_rkey)); + parakeet_db::utils::tid::encode_tid(item_rkey)); let subject = self.profile_entity.actor_to_profile_view(subject_actor); use jacquard_common::IntoStatic; diff --git a/parakeet/src/entities/ext/actor.rs b/parakeet/src/entities/ext/actor.rs index 8c188ab4..26e24f34 100644 --- a/parakeet/src/entities/ext/actor.rs +++ b/parakeet/src/entities/ext/actor.rs @@ -1,7 +1,7 @@ use parakeet_db::{ models::Actor, types::ActorSyncState, - composite_types::Follow, + composite::Follow, }; /// Extension trait for Actor to add convenience methods and AT Protocol conversions @@ -193,7 +193,7 @@ impl ActorExt for Actor { fn pinned_post_uri(&self) -> Option { self.profile_pinned_post_rkey.map(|rkey| { - let tid = parakeet_db::tid_util::encode_tid(rkey); + let tid = parakeet_db::utils::tid::encode_tid(rkey); format!("at://{}/app.bsky.feed.post/{}", self.did, tid) }) } diff --git a/parakeet/src/entities/ext/post.rs b/parakeet/src/entities/ext/post.rs index 3871ab14..5adfc4d4 100644 --- a/parakeet/src/entities/ext/post.rs +++ b/parakeet/src/entities/ext/post.rs @@ -1,4 +1,4 @@ -use parakeet_db::models::Post; +use parakeet_db::domain::Post; /// Extension trait for Post to add convenience methods and AT Protocol conversions pub trait PostExt { @@ -50,7 +50,7 @@ impl PostExt for Post { format!( "at://{}/app.bsky.feed.post/{}", author_did, - parakeet_db::tid_util::encode_tid(self.rkey) + parakeet_db::utils::tid::encode_tid(self.rkey) ) } diff --git a/parakeet/src/xrpc/app_bsky/bookmark.rs b/parakeet/src/xrpc/app_bsky/bookmark.rs index 941d52dd..71ea52a7 100644 --- a/parakeet/src/xrpc/app_bsky/bookmark.rs +++ b/parakeet/src/xrpc/app_bsky/bookmark.rs @@ -47,7 +47,7 @@ pub async fn create_bookmark( .map_err(|_| StatusCode::NOT_FOUND)?; // Decode TID - let rkey = parakeet_db::tid_util::decode_tid(rkey_str) + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str) .map_err(|_| StatusCode::BAD_REQUEST)?; // Update bookmarks array on actor @@ -56,7 +56,7 @@ pub async fn create_bookmark( // Create new bookmark composite type let new_bookmark = format!("({},{},{})", - parakeet_db::tid_util::decode_tid(¶keet_db::tid_util::timestamp_to_tid(chrono::Utc::now())).unwrap_or(0), // Generate new rkey for the bookmark + parakeet_db::utils::tid::decode_tid(¶keet_db::utils::tid::timestamp_to_tid(chrono::Utc::now())).unwrap_or(0), // Generate new rkey for the bookmark post_actor_id, rkey ); @@ -119,7 +119,7 @@ pub async fn delete_bookmark( .map_err(|_| StatusCode::NOT_FOUND)?; // Decode TID - let rkey = parakeet_db::tid_util::decode_tid(rkey_str) + let rkey = parakeet_db::utils::tid::decode_tid(rkey_str) .map_err(|_| StatusCode::BAD_REQUEST)?; // Remove bookmark from actor's array @@ -194,12 +194,12 @@ pub async fn get_bookmarks( let uri = format!( "at://{}/app.bsky.feed.post/{}", post_did, - parakeet_db::tid_util::encode_tid(post_rkey) + parakeet_db::utils::tid::encode_tid(post_rkey) ); // Create StrongRef for the subject // Convert CID bytes to string, then parse to Cid - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&cid) + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&cid) .unwrap_or_else(|| "bafyreigrey4aogz7sq5bxfaiwlcieaivxscvgs5ivgqczecmvz6jhmnxq".to_string()); // Default CID use jacquard_common::IntoStatic; diff --git a/parakeet/src/xrpc/app_bsky/feed/get_timeline.rs b/parakeet/src/xrpc/app_bsky/feed/get_timeline.rs index e6c429db..dc2d7815 100644 --- a/parakeet/src/xrpc/app_bsky/feed/get_timeline.rs +++ b/parakeet/src/xrpc/app_bsky/feed/get_timeline.rs @@ -41,8 +41,8 @@ pub async fn get_timeline( .map(|c| c.as_ref()) .and_then(|c| datetime_cursor(Some(&c.to_string()))) .map(|dt| { - let tid_str = parakeet_db::tid_util::timestamp_to_tid(dt); - parakeet_db::tid_util::decode_tid(&tid_str).unwrap_or(0) + let tid_str = parakeet_db::utils::tid::timestamp_to_tid(dt); + parakeet_db::utils::tid::decode_tid(&tid_str).unwrap_or(0) }); // Get followed profiles with their post rkeys (all cached) @@ -107,7 +107,7 @@ pub async fn get_timeline( // Build cursor from last item (if we have more pages) let cursor = if has_next && timeline_items.len() > limit as usize { let last_rkey = timeline_items[limit as usize - 1].0; - let timestamp = parakeet_db::tid_util::tid_to_datetime(last_rkey); + let timestamp = parakeet_db::utils::tid::tid_to_datetime(last_rkey); Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) } else { None @@ -173,7 +173,7 @@ pub async fn get_timeline( uri: Some(jacquard_common::types::aturi::AtUri::new(&format!( "at://{}/app.bsky.feed.repost/{}", reposter.did, - parakeet_db::tid_util::encode_tid(rkey) + parakeet_db::utils::tid::encode_tid(rkey) )).unwrap()), cid: None, // TODO: Add CID if needed indexed_at: jacquard_common::types::datetime::Datetime::from(indexed_at.fixed_offset()), @@ -236,8 +236,8 @@ pub async fn get_author_feed( .map(|c| c.as_ref()) .and_then(|c| datetime_cursor(Some(&c.to_string()))) .map(|dt| { - let tid_str = parakeet_db::tid_util::timestamp_to_tid(dt); - parakeet_db::tid_util::decode_tid(&tid_str).unwrap_or(0) + let tid_str = parakeet_db::utils::tid::timestamp_to_tid(dt); + parakeet_db::utils::tid::decode_tid(&tid_str).unwrap_or(0) }); // Use ProfileEntity to get author's posts @@ -262,7 +262,7 @@ pub async fn get_author_feed( // Build cursor from last post let cursor = if has_next && posts_to_return.len() == limit as usize { let last_rkey = posts_to_return[posts_to_return.len() - 1].1; - let timestamp = parakeet_db::tid_util::tid_to_datetime(last_rkey); + let timestamp = parakeet_db::utils::tid::tid_to_datetime(last_rkey); Some(timestamp.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) } else { None diff --git a/parakeet/src/xrpc/app_bsky/feed/likes.rs b/parakeet/src/xrpc/app_bsky/feed/likes.rs index 6aae6305..e0da3526 100644 --- a/parakeet/src/xrpc/app_bsky/feed/likes.rs +++ b/parakeet/src/xrpc/app_bsky/feed/likes.rs @@ -47,7 +47,7 @@ pub async fn get_actor_likes( .last() .map(|(_, rkey)| { // Convert TID to timestamp for cursor - let dt = parakeet_db::tid_util::tid_to_datetime(*rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(*rkey); dt.to_rfc3339_opts(chrono::SecondsFormat::Millis, true) }); @@ -56,7 +56,7 @@ pub async fn get_actor_likes( for (post_actor_id, post_rkey) in &results { let post_did = state.profile_entity.get_did_by_id(*post_actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", post_actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(*post_rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(*post_rkey); let uri = format!("at://{}/app.bsky.feed.post/{}", post_did, rkey_str); post_uris.push(uri); } @@ -124,7 +124,7 @@ pub async fn get_likes( .map_err(|_| Error::not_found())?; // Decode TID - let post_rkey = parakeet_db::tid_util::decode_tid(post_rkey_str) + let post_rkey = parakeet_db::utils::tid::decode_tid(post_rkey_str) .map_err(|_| Error::invalid_request(Some("Invalid TID".to_string())))?; // Parse cursor as timestamp diff --git a/parakeet/src/xrpc/app_bsky/feed/posts/queries.rs b/parakeet/src/xrpc/app_bsky/feed/posts/queries.rs index cc499d54..b35df153 100644 --- a/parakeet/src/xrpc/app_bsky/feed/posts/queries.rs +++ b/parakeet/src/xrpc/app_bsky/feed/posts/queries.rs @@ -134,7 +134,7 @@ pub async fn get_quotes( .map_err(|_| crate::common::errors::Error::not_found())?; // Decode rkey - let embed_rkey = parakeet_db::tid_util::decode_tid(embed_rkey_str) + let embed_rkey = parakeet_db::utils::tid::decode_tid(embed_rkey_str) .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; // Parse cursor @@ -166,7 +166,7 @@ pub async fn get_quotes( let uri = format!( "at://{}/app.bsky.feed.post/{}", author_did, - parakeet_db::tid_util::encode_tid(*rkey) + parakeet_db::utils::tid::encode_tid(*rkey) ); // Get post view @@ -238,7 +238,7 @@ pub async fn get_reposted_by( .map_err(|_| crate::common::errors::Error::not_found())?; // Decode rkey - let post_rkey = parakeet_db::tid_util::decode_tid(post_rkey_str) + let post_rkey = parakeet_db::utils::tid::decode_tid(post_rkey_str) .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; // Parse cursor diff --git a/parakeet/src/xrpc/app_bsky/feed/posts/threads.rs b/parakeet/src/xrpc/app_bsky/feed/posts/threads.rs index 4d99a826..4461e68f 100644 --- a/parakeet/src/xrpc/app_bsky/feed/posts/threads.rs +++ b/parakeet/src/xrpc/app_bsky/feed/posts/threads.rs @@ -79,7 +79,7 @@ pub async fn get_post_thread( let anchor_actor_id = state.profile_entity.resolve_identifier(anchor_did).await .map_err(|_| Error::actor_not_found(anchor_did))?; - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) .map_err(|_| Error::invalid_request(Some("Invalid rkey".to_string())))?; // Get root info for parent query filtering @@ -119,7 +119,7 @@ pub async fn get_post_thread( let root_actor_id = state.profile_entity.resolve_identifier(root_did).await .map_err(|_| Error::actor_not_found(root_did))?; - let root_rkey = parakeet_db::tid_util::decode_tid(root_rkey_base32).ok(); + let root_rkey = parakeet_db::utils::tid::decode_tid(root_rkey_base32).ok(); root_rkey.map(|rkey| (root_actor_id, rkey)) } else { None @@ -152,7 +152,7 @@ pub async fn get_post_thread( for item in &parents { let did = state.profile_entity.get_did_by_id(item.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); all_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); } @@ -160,7 +160,7 @@ pub async fn get_post_thread( for item in &children { let did = state.profile_entity.get_did_by_id(item.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); all_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); } diff --git a/parakeet/src/xrpc/app_bsky/feed/search.rs b/parakeet/src/xrpc/app_bsky/feed/search.rs index 916ce38a..dfe06beb 100644 --- a/parakeet/src/xrpc/app_bsky/feed/search.rs +++ b/parakeet/src/xrpc/app_bsky/feed/search.rs @@ -75,7 +75,7 @@ pub async fn search_posts( for (actor_id, rkey) in &results { let did = state.profile_entity.get_did_by_id(*actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(*rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(*rkey); post_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); } @@ -142,7 +142,7 @@ pub async fn search_posts_skeleton( for (actor_id, rkey) in results { let author_did = state.profile_entity.get_did_by_id(actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(rkey); let uri = format!("at://{}/app.bsky.feed.post/{}", author_did, rkey_str); posts.push(uri); } diff --git a/parakeet/src/xrpc/app_bsky/graph/lists.rs b/parakeet/src/xrpc/app_bsky/graph/lists.rs index e0f7e4e6..d5b9cbe4 100644 --- a/parakeet/src/xrpc/app_bsky/graph/lists.rs +++ b/parakeet/src/xrpc/app_bsky/graph/lists.rs @@ -102,7 +102,7 @@ pub async fn get_list( .ok_or_else(|| Error::not_found())?; // Parse the list URI to get actor_id and rkey for querying items - let (did, _collection, rkey_str) = parakeet_db::at_uri_util::parse_at_uri(&query.list) + let (did, _collection, rkey_str) = parakeet_db::utils::at_uri::parse_at_uri(&query.list) .ok_or_else(|| Error::not_found())?; // Resolve DID to actor_id @@ -172,7 +172,7 @@ pub async fn get_list( let item_uri = format!( "at://{}/app.bsky.graph.listitem/{}", item_did, - parakeet_db::tid_util::encode_tid(rkey) + parakeet_db::utils::tid::encode_tid(rkey) ); Some(ListItemView { diff --git a/parakeet/src/xrpc/app_bsky/graph/relations.rs b/parakeet/src/xrpc/app_bsky/graph/relations.rs index 8f761cc5..22eb6c85 100644 --- a/parakeet/src/xrpc/app_bsky/graph/relations.rs +++ b/parakeet/src/xrpc/app_bsky/graph/relations.rs @@ -29,7 +29,7 @@ pub async fn get_blocks( // Build cursor from last result let cursor = blocks.last().and_then(|b| { - let dt = parakeet_db::tid_util::tid_to_datetime(b.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(b.rkey); Some(dt.timestamp_millis().to_string()) }); @@ -95,7 +95,7 @@ pub async fn get_followers( if let Some(cursor_ts) = cursor_value { followers.retain(|f| { // f.rkey is a TID that needs conversion - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); dt < cursor_ts }); } @@ -110,7 +110,7 @@ pub async fn get_followers( let cursor = if has_next { followers.last().map(|f| { // Convert TID to timestamp for cursor - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); dt.timestamp_millis().to_string() }) } else { @@ -153,7 +153,7 @@ pub async fn get_follows( // Build cursor from last result let cursor = results.last().map(|f| { - let dt = parakeet_db::tid_util::tid_to_datetime(f.rkey); + let dt = parakeet_db::utils::tid::tid_to_datetime(f.rkey); dt.timestamp_millis().to_string() }); diff --git a/parakeet/src/xrpc/app_bsky/graph/starter_packs.rs b/parakeet/src/xrpc/app_bsky/graph/starter_packs.rs index 9c48fc4d..d4032314 100644 --- a/parakeet/src/xrpc/app_bsky/graph/starter_packs.rs +++ b/parakeet/src/xrpc/app_bsky/graph/starter_packs.rs @@ -67,7 +67,7 @@ pub async fn get_actor_starter_packs( let uris: Vec = results .iter() .map(|r| { - let rkey_str = parakeet_db::tid_util::encode_tid(r.2); + let rkey_str = parakeet_db::utils::tid::encode_tid(r.2); format!("at://{}/app.bsky.graph.starterpack/{}", actor_did, rkey_str) }) .collect(); diff --git a/parakeet/src/xrpc/app_bsky/graph/thread_mutes.rs b/parakeet/src/xrpc/app_bsky/graph/thread_mutes.rs index 1821b263..2b2bee3b 100644 --- a/parakeet/src/xrpc/app_bsky/graph/thread_mutes.rs +++ b/parakeet/src/xrpc/app_bsky/graph/thread_mutes.rs @@ -33,7 +33,7 @@ pub async fn mute_thread( let (root_did, _collection, rkey_str) = (parts[0], parts[1], parts[2]); // Get root post's actor_id and rkey (natural keys) - let rkey_bigint = parakeet_db::tid_util::decode_tid(rkey_str) + let rkey_bigint = parakeet_db::utils::tid::decode_tid(rkey_str) .map_err(|_| Error::invalid_request(Some("Invalid TID in root URI".into())))?; let root_post_actor_id = state.profile_entity.resolve_identifier(root_did).await @@ -87,7 +87,7 @@ pub async fn unmute_thread( let (root_did, _collection, rkey_str) = (parts[0], parts[1], parts[2]); // Get root post's actor_id and rkey (natural keys) - let rkey_bigint = parakeet_db::tid_util::decode_tid(rkey_str) + let rkey_bigint = parakeet_db::utils::tid::decode_tid(rkey_str) .map_err(|_| Error::invalid_request(Some("Invalid TID in root URI".into())))?; let root_post_actor_id = state.profile_entity.resolve_identifier(root_did).await diff --git a/parakeet/src/xrpc/app_bsky/notification.rs b/parakeet/src/xrpc/app_bsky/notification.rs index b5ed0780..2978780e 100644 --- a/parakeet/src/xrpc/app_bsky/notification.rs +++ b/parakeet/src/xrpc/app_bsky/notification.rs @@ -281,7 +281,7 @@ pub async fn list_notifications( uri: jacquard_common::types::string::AtUri::new(&uri).unwrap().into_static(), // Use the real CID from database (already stored for all record types) cid: jacquard_common::types::string::Cid::new( - parakeet_db::cid_util::digest_to_record_cid_string(¬if.record_cid) + parakeet_db::utils::cid::digest_to_record_cid_string(¬if.record_cid) .unwrap_or_else(|| String::from("bafyrei_invalid_cid")) .as_bytes() ).unwrap().into_static(), @@ -501,14 +501,14 @@ fn build_notification_record( use parakeet_db::types::NotificationRecordType; // Calculate created_at from record rkey for all record types - let created_at = parakeet_db::tid_util::tid_to_datetime(notif.record_rkey); + let created_at = parakeet_db::utils::tid::tid_to_datetime(notif.record_rkey); match notif.record_type { NotificationRecordType::Like => { // Construct subject URI from notification's subject fields if let (Some(subject_actor_id), Some(subject_rkey)) = (notif.subject_actor_id, notif.subject_rkey) { if let Some(subject_did) = actor_did_map.get(&subject_actor_id) { - let encoded_rkey = parakeet_db::tid_util::encode_tid(subject_rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(subject_rkey); let subject_uri = format!("at://{}/app.bsky.feed.post/{}", subject_did, encoded_rkey); return serde_json::json!({ @@ -525,7 +525,7 @@ fn build_notification_record( // Construct subject post URI from notification's subject fields if let (Some(subject_actor_id), Some(subject_rkey)) = (notif.subject_actor_id, notif.subject_rkey) { if let Some(subject_did) = actor_did_map.get(&subject_actor_id) { - let encoded_rkey = parakeet_db::tid_util::encode_tid(subject_rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(subject_rkey); let post_uri = format!("at://{}/app.bsky.feed.post/{}", subject_did, encoded_rkey); return serde_json::json!({ @@ -564,7 +564,7 @@ fn build_notification_record( let parent_uri = match (parent_actor_id, parent_rkey) { (Some(actor_id), Some(rkey)) => { actor_did_map.get(actor_id).map(|did| { - let encoded_rkey = parakeet_db::tid_util::encode_tid(*rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(*rkey); format!("at://{}/app.bsky.feed.post/{}", did, encoded_rkey) }) } @@ -573,7 +573,7 @@ fn build_notification_record( let root_uri = match (root_actor_id, root_rkey) { (Some(actor_id), Some(rkey)) => { actor_did_map.get(actor_id).map(|did| { - let encoded_rkey = parakeet_db::tid_util::encode_tid(*rkey); + let encoded_rkey = parakeet_db::utils::tid::encode_tid(*rkey); format!("at://{}/app.bsky.feed.post/{}", did, encoded_rkey) }) } diff --git a/parakeet/src/xrpc/app_bsky/unspecced/handlers.rs b/parakeet/src/xrpc/app_bsky/unspecced/handlers.rs index ff2c6254..3a54f3f5 100644 --- a/parakeet/src/xrpc/app_bsky/unspecced/handlers.rs +++ b/parakeet/src/xrpc/app_bsky/unspecced/handlers.rs @@ -8,7 +8,7 @@ use jacquard_api::app_bsky::actor::ProfileView; use jacquard_api::app_bsky::feed::GeneratorView; use jacquard_api::app_bsky::graph::StarterPackViewBasic; use jacquard_common::IntoStatic; -use parakeet_db::models::ProfileStats; +use parakeet_db::domain::ProfileStats; use serde::{Deserialize, Serialize}; // ======== getTrendingTopics ======== diff --git a/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/other_replies.rs b/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/other_replies.rs index e1bca5d9..0b444d43 100644 --- a/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/other_replies.rs +++ b/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/other_replies.rs @@ -91,7 +91,7 @@ pub async fn get_post_thread_other_v2( // Get actor_id from cache (should be cached after hydrate_post) let cached_actor = state.id_cache.get_actor_id(anchor_did).await .ok_or_else(|| Error::server_error(Some("Actor not in cache")))?; - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) .map_err(|_| Error::invalid_request(Some("Invalid rkey".to_string())))?; // Get additional replies that weren't included in the main thread view @@ -108,7 +108,7 @@ pub async fn get_post_thread_other_v2( for item in &replies { let did = state.profile_entity.get_did_by_id(item.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); reply_uris.push(format!("at://{}/app.bsky.feed.post/{}", did, rkey_str)); } let replies_hydrated = std::collections::HashMap::new(); // TODO: Fix hydration @@ -129,13 +129,13 @@ pub async fn get_post_thread_other_v2( // Build parent URI let parent_did = state.profile_entity.get_did_by_id(parent_actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", parent_actor_id)); - let parent_rkey_str = parakeet_db::tid_util::encode_tid(parent_rkey); + let parent_rkey_str = parakeet_db::utils::tid::encode_tid(parent_rkey); let parent_uri = format!("at://{}/app.bsky.feed.post/{}", parent_did, parent_rkey_str); // Build this post's URI let did = state.profile_entity.get_did_by_id(reply.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", reply.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(reply.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(reply.rkey); let at_uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); replies_by_parent diff --git a/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/thread_builder.rs b/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/thread_builder.rs index 7f46c7b0..a1e784eb 100644 --- a/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/thread_builder.rs +++ b/parakeet/src/xrpc/app_bsky/unspecced/thread_v2/thread_builder.rs @@ -168,7 +168,7 @@ impl ThreadBuilder<'_> { let cached_actor = self.id_cache.get_actor_id(anchor_did).await .ok_or_else(|| crate::common::errors::Error::server_error(Some("Actor not in cache")))?; - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; // Get root actor_id if we have a root URI @@ -179,7 +179,7 @@ impl ThreadBuilder<'_> { let root_rkey_base32 = root_parts[2]; let root_cached = self.id_cache.get_actor_id(root_did).await .ok_or_else(|| crate::common::errors::Error::server_error(Some("Root actor not in cache")))?; - let root_rkey = parakeet_db::tid_util::decode_tid(root_rkey_base32) + let root_rkey = parakeet_db::utils::tid::decode_tid(root_rkey_base32) .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid root rkey".to_string())))?; (root_cached.actor_id, root_rkey) } else { @@ -216,7 +216,7 @@ impl ThreadBuilder<'_> { for item in &parents { let did = self.profile_entity.get_did_by_id(item.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); let uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); parent_uris.push(uri.clone()); parent_uri_by_index.push(uri); @@ -326,7 +326,7 @@ impl ThreadBuilder<'_> { let cached_actor = self.id_cache.get_actor_id(anchor_did).await .ok_or_else(|| crate::common::errors::Error::server_error(Some("Actor not in cache")))?; - let anchor_rkey = parakeet_db::tid_util::decode_tid(anchor_rkey_base32) + let anchor_rkey = parakeet_db::utils::tid::decode_tid(anchor_rkey_base32) .map_err(|_| crate::common::errors::Error::invalid_request(Some("Invalid rkey".to_string())))?; self.post_entity.get_thread_children_by_arrays( @@ -355,7 +355,7 @@ impl ThreadBuilder<'_> { for item in &replies { let did = self.profile_entity.get_did_by_id(item.actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", item.actor_id)); - let rkey_str = parakeet_db::tid_util::encode_tid(item.rkey); + let rkey_str = parakeet_db::utils::tid::encode_tid(item.rkey); let uri = format!("at://{}/app.bsky.feed.post/{}", did, rkey_str); reply_uris.push(uri.clone()); reply_uri_map.insert((item.actor_id, item.rkey), uri); @@ -378,7 +378,7 @@ impl ThreadBuilder<'_> { // Build parent URI let parent_did = self.profile_entity.get_did_by_id(parent_actor_id).await .unwrap_or_else(|_| format!("did:plc:unknown{}", parent_actor_id)); - let parent_rkey_str = parakeet_db::tid_util::encode_tid(parent_rkey); + let parent_rkey_str = parakeet_db::utils::tid::encode_tid(parent_rkey); let parent_uri = format!("at://{}/app.bsky.feed.post/{}", parent_did, parent_rkey_str); let child_uri = reply_uri_map.get(&(reply.actor_id, reply.rkey)) diff --git a/parakeet/src/xrpc/com_atproto/repo.rs b/parakeet/src/xrpc/com_atproto/repo.rs index 3cf405d0..a3c968f3 100644 --- a/parakeet/src/xrpc/com_atproto/repo.rs +++ b/parakeet/src/xrpc/com_atproto/repo.rs @@ -52,7 +52,7 @@ pub async fn get_record( } // Decode base32 TID to bigint - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; let result: FeedGenRecord = diesel_async::RunQueryDsl::get_result( @@ -73,7 +73,7 @@ pub async fn get_record( ) .await?; - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); ( @@ -86,7 +86,7 @@ pub async fn get_record( } "app.bsky.feed.post" => { // Decode base32 TID to bigint - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; // Use raw SQL to get CID and record from posts table @@ -108,7 +108,7 @@ pub async fn get_record( .await?; // Convert real CID from database to string - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); (cid_str, result.record) @@ -124,7 +124,7 @@ pub async fn get_record( } // Decode base32 TID to bigint - let rkey_bigint = parakeet_db::tid_util::decode_tid(&query.rkey) + let rkey_bigint = parakeet_db::utils::tid::decode_tid(&query.rkey) .map_err(|_| Error::invalid_request(Some("Invalid rkey format".to_string())))?; let result: StarterPackRecord = diesel_async::RunQueryDsl::get_result( @@ -144,7 +144,7 @@ pub async fn get_record( ) .await?; - let cid_str = parakeet_db::cid_util::digest_to_record_cid_string(&result.cid) + let cid_str = parakeet_db::utils::cid::digest_to_record_cid_string(&result.cid) .unwrap_or_else(|| String::from("bafyrei_invalid_cid")); (cid_str, result.record) diff --git a/parakeet/src/xrpc/community_lexicon/bookmarks.rs b/parakeet/src/xrpc/community_lexicon/bookmarks.rs index e4402b69..35399f92 100644 --- a/parakeet/src/xrpc/community_lexicon/bookmarks.rs +++ b/parakeet/src/xrpc/community_lexicon/bookmarks.rs @@ -54,7 +54,7 @@ pub async fn get_actor_bookmarks( impl BookmarkRecord { fn created_at(&self) -> chrono::DateTime { - parakeet_db::tid_util::tid_to_datetime(self.rkey) + parakeet_db::utils::tid::tid_to_datetime(self.rkey) } } -- 2.51.2