Parakeet is a Rust-based Bluesky AppServer aiming to implement most of the functionality required to support the Bluesky client
appview atproto bluesky rust appserver

feat: via for likes and reposts

+43 -12
+2 -2
consumer/src/backfill/mod.rs
··· 336 337 #[derive(Debug, Default)] 338 struct CopyStore { 339 - likes: Vec<(String, records::StrongRef, DateTime<Utc>)>, 340 posts: Vec<(String, Cid, records::AppBskyFeedPost)>, 341 - reposts: Vec<(String, records::StrongRef, DateTime<Utc>)>, 342 blocks: Vec<(String, String, DateTime<Utc>)>, 343 follows: Vec<(String, String, DateTime<Utc>)>, 344 list_items: Vec<(String, records::AppBskyGraphListItem)>,
··· 336 337 #[derive(Debug, Default)] 338 struct CopyStore { 339 + likes: Vec<(String, records::StrongRef, Option<records::StrongRef>, DateTime<Utc>)>, 340 posts: Vec<(String, Cid, records::AppBskyFeedPost)>, 341 + reposts: Vec<(String, records::StrongRef, Option<records::StrongRef>, DateTime<Utc>)>, 342 blocks: Vec<(String, String, DateTime<Utc>)>, 343 follows: Vec<(String, String, DateTime<Utc>)>, 344 list_items: Vec<(String, records::AppBskyGraphListItem)>,
+2 -2
consumer/src/backfill/repo.rs
··· 131 deltas.incr(&rec.subject.uri, AggregateType::Like).await; 132 133 copies.push_record(&at_uri, cid); 134 - copies.likes.push((at_uri, rec.subject, rec.created_at)); 135 } 136 RecordTypes::AppBskyFeedPost(rec) => { 137 let maybe_reply = rec.reply.as_ref().map(|v| v.parent.uri.clone()); ··· 167 deltas.incr(&rec.subject.uri, AggregateType::Repost).await; 168 169 copies.push_record(&at_uri, cid); 170 - copies.reposts.push((at_uri, rec.subject, rec.created_at)); 171 } 172 RecordTypes::AppBskyGraphBlock(rec) => { 173 copies.push_record(&at_uri, cid);
··· 131 deltas.incr(&rec.subject.uri, AggregateType::Like).await; 132 133 copies.push_record(&at_uri, cid); 134 + copies.likes.push((at_uri, rec.subject, rec.via, rec.created_at)); 135 } 136 RecordTypes::AppBskyFeedPost(rec) => { 137 let maybe_reply = rec.reply.as_ref().map(|v| v.parent.uri.clone()); ··· 167 deltas.incr(&rec.subject.uri, AggregateType::Repost).await; 168 169 copies.push_record(&at_uri, cid); 170 + copies.reposts.push((at_uri, rec.subject, rec.via, rec.created_at)); 171 } 172 RecordTypes::AppBskyGraphBlock(rec) => { 173 copies.push_record(&at_uri, cid);
+15 -5
consumer/src/db/copy.rs
··· 14 Type::TEXT, 15 Type::TEXT, 16 Type::TEXT, 17 Type::TIMESTAMP, 18 ]; 19 - type StrongRefRow = (String, records::StrongRef, DateTime<Utc>); 20 21 // SubjectRefs are used in both blocks and follows 22 const SUBJECT_TYPES: &[Type] = &[Type::TEXT, Type::TEXT, Type::TEXT, Type::TIMESTAMP]; ··· 39 40 let writer = conn 41 .copy_in( 42 - "COPY likes_tmp (at_uri, did, subject, subject_cid, created_at) FROM STDIN (FORMAT binary)", 43 ) 44 .await?; 45 let writer = BinaryCopyInWriter::new(writer, STRONGREF_TYPES); ··· 47 pin_mut!(writer); 48 49 for row in data { 50 let writer = writer.as_mut(); 51 writer 52 .write(&[ ··· 54 &did, 55 &row.1.uri, 56 &row.1.cid.to_string(), 57 - &row.2.naive_utc(), 58 ]) 59 .await?; 60 } ··· 82 83 let writer = conn 84 .copy_in( 85 - "COPY reposts_tmp (at_uri, did, post, post_cid, created_at) FROM STDIN (FORMAT binary)", 86 ) 87 .await?; 88 let writer = BinaryCopyInWriter::new(writer, STRONGREF_TYPES); ··· 90 pin_mut!(writer); 91 92 for row in data { 93 let writer = writer.as_mut(); 94 writer 95 .write(&[ ··· 97 &did, 98 &row.1.uri, 99 &row.1.cid.to_string(), 100 - &row.2.naive_utc(), 101 ]) 102 .await?; 103 }
··· 14 Type::TEXT, 15 Type::TEXT, 16 Type::TEXT, 17 + Type::TEXT, 18 + Type::TEXT, 19 Type::TIMESTAMP, 20 ]; 21 + type StrongRefRow = (String, records::StrongRef, Option<records::StrongRef>, DateTime<Utc>); 22 23 // SubjectRefs are used in both blocks and follows 24 const SUBJECT_TYPES: &[Type] = &[Type::TEXT, Type::TEXT, Type::TEXT, Type::TIMESTAMP]; ··· 41 42 let writer = conn 43 .copy_in( 44 + "COPY likes_tmp (at_uri, did, subject, subject_cid, via_uri, via_cid, created_at) FROM STDIN (FORMAT binary)", 45 ) 46 .await?; 47 let writer = BinaryCopyInWriter::new(writer, STRONGREF_TYPES); ··· 49 pin_mut!(writer); 50 51 for row in data { 52 + let (via_uri, via_cid) = strongref_to_parts(row.2.as_ref()); 53 + 54 let writer = writer.as_mut(); 55 writer 56 .write(&[ ··· 58 &did, 59 &row.1.uri, 60 &row.1.cid.to_string(), 61 + &via_uri, 62 + &via_cid, 63 + &row.3.naive_utc(), 64 ]) 65 .await?; 66 } ··· 88 89 let writer = conn 90 .copy_in( 91 + "COPY reposts_tmp (at_uri, did, post, post_cid, via_uri, via_cid, created_at) FROM STDIN (FORMAT binary)", 92 ) 93 .await?; 94 let writer = BinaryCopyInWriter::new(writer, STRONGREF_TYPES); ··· 96 pin_mut!(writer); 97 98 for row in data { 99 + let (via_uri, via_cid) = strongref_to_parts(row.2.as_ref()); 100 + 101 let writer = writer.as_mut(); 102 writer 103 .write(&[ ··· 105 &did, 106 &row.1.uri, 107 &row.1.cid.to_string(), 108 + &via_uri, 109 + &via_cid, 110 + &row.3.naive_utc(), 111 ]) 112 .await?; 113 }
+9 -3
consumer/src/db/record.rs
··· 156 repo: &str, 157 rec: AppBskyFeedLike, 158 ) -> PgExecResult { 159 conn.execute( 160 - "INSERT INTO likes (at_uri, did, subject, subject_cid, created_at) VALUES ($1, $2, $3, $4, $5)", 161 - &[&at_uri, &repo, &rec.subject.uri, &rec.subject.cid.to_string(), &rec.created_at] 162 ).await 163 } 164 ··· 523 repo: &str, 524 rec: AppBskyFeedRepost, 525 ) -> PgExecResult { 526 conn.execute( 527 - "INSERT INTO reposts (at_uri, did, post, post_cid, created_at) VALUES ($1, $2, $3, $4, $5)", 528 &[ 529 &at_uri, 530 &repo, 531 &rec.subject.uri, 532 &rec.subject.cid.to_string(), 533 &rec.created_at, 534 ], 535 )
··· 156 repo: &str, 157 rec: AppBskyFeedLike, 158 ) -> PgExecResult { 159 + let (via_uri, via_cid) = strongref_to_parts(rec.via.as_ref()); 160 + 161 conn.execute( 162 + "INSERT INTO likes (at_uri, did, subject, subject_cid, via_uri, via_cid, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)", 163 + &[&at_uri, &repo, &rec.subject.uri, &rec.subject.cid.to_string(), &via_uri, &via_cid, &rec.created_at] 164 ).await 165 } 166 ··· 525 repo: &str, 526 rec: AppBskyFeedRepost, 527 ) -> PgExecResult { 528 + let (via_uri, via_cid) = strongref_to_parts(rec.via.as_ref()); 529 + 530 conn.execute( 531 + "INSERT INTO reposts (at_uri, did, post, post_cid, via_uri, via_cid, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)", 532 &[ 533 &at_uri, 534 &repo, 535 &rec.subject.uri, 536 &rec.subject.cid.to_string(), 537 + &via_uri, 538 + &via_cid, 539 &rec.created_at, 540 ], 541 )
+2
consumer/src/indexer/records.rs
··· 206 pub struct AppBskyFeedLike { 207 pub subject: StrongRef, 208 pub created_at: DateTime<Utc>, 209 } 210 211 #[derive(Debug, Deserialize, Serialize)] ··· 260 pub struct AppBskyFeedRepost { 261 pub subject: StrongRef, 262 pub created_at: DateTime<Utc>, 263 } 264 265 #[derive(Debug, Deserialize, Serialize)]
··· 206 pub struct AppBskyFeedLike { 207 pub subject: StrongRef, 208 pub created_at: DateTime<Utc>, 209 + pub via: Option<StrongRef>, 210 } 211 212 #[derive(Debug, Deserialize, Serialize)] ··· 261 pub struct AppBskyFeedRepost { 262 pub subject: StrongRef, 263 pub created_at: DateTime<Utc>, 264 + pub via: Option<StrongRef>, 265 } 266 267 #[derive(Debug, Deserialize, Serialize)]
+2
migrations/2025-06-18-200415_like-repost-via/down.sql
···
··· 1 + alter table likes drop column via_uri, drop column via_cid; 2 + alter table reposts drop column via_uri, drop column via_cid;
+7
migrations/2025-06-18-200415_like-repost-via/up.sql
···
··· 1 + alter table likes 2 + add column via_uri text, 3 + add column via_cid text; 4 + 5 + alter table reposts 6 + add column via_uri text, 7 + add column via_cid text;
+4
parakeet-db/src/schema.rs
··· 125 subject_cid -> Text, 126 created_at -> Timestamptz, 127 indexed_at -> Timestamp, 128 } 129 } 130 ··· 283 post_cid -> Text, 284 created_at -> Timestamptz, 285 indexed_at -> Timestamp, 286 } 287 } 288
··· 125 subject_cid -> Text, 126 created_at -> Timestamptz, 127 indexed_at -> Timestamp, 128 + via_uri -> Nullable<Text>, 129 + via_cid -> Nullable<Text>, 130 } 131 } 132 ··· 285 post_cid -> Text, 286 created_at -> Timestamptz, 287 indexed_at -> Timestamp, 288 + via_uri -> Nullable<Text>, 289 + via_cid -> Nullable<Text>, 290 } 291 } 292