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