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

Timelines #4

merged opened by mia.omg.lol targeting main from push-znlunvslzwrw

This is "old" style timelines - the ones that show you all post and reply activity from people you follow. We may implement new style timelines in the future, but they're more complicated (and people quite like the old one anyways)

Labels

None yet.

component

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:63y3oh7iakdueqhlj6trojbq/sh.tangled.repo.pull/3meyvvfm2cz22
+83 -4
Diff #1
-1
README.md
··· 11 11 - Notifications 12 12 - Search 13 13 - Pinned Posts 14 - - The Timeline 15 14 - Monitoring: metrics, tracing, and health checks. 16 15 17 16 ## The Code
+1
crates/parakeet/src/xrpc/app_bsky/feed/mod.rs
··· 1 1 pub mod feedgen; 2 2 pub mod likes; 3 3 pub mod posts; 4 + pub mod timeline;
+2 -2
crates/parakeet/src/xrpc/app_bsky/feed/posts.rs
··· 33 33 #[derive(Debug, Serialize)] 34 34 pub struct FeedRes { 35 35 #[serde(skip_serializing_if = "Option::is_none")] 36 - cursor: Option<String>, 37 - feed: Vec<FeedViewPost>, 36 + pub cursor: Option<String>, 37 + pub feed: Vec<FeedViewPost>, 38 38 } 39 39 40 40 #[derive(Debug, Deserialize)]
+79
crates/parakeet/src/xrpc/app_bsky/feed/timeline.rs
··· 1 + use super::posts::FeedRes; 2 + use crate::hydration::posts::RawFeedItem; 3 + use crate::hydration::StatefulHydrator; 4 + use crate::xrpc::error::XrpcResult; 5 + use crate::xrpc::extract::{AtpAcceptLabelers, AtpAuth}; 6 + use crate::xrpc::{datetime_cursor, CursorQuery}; 7 + use crate::GlobalState; 8 + use axum::extract::{Query, State}; 9 + use axum::Json; 10 + use diesel::prelude::*; 11 + use diesel_async::RunQueryDsl; 12 + use parakeet_db::{models, schema}; 13 + 14 + // okay so there's debate as to if the TL should show all posts from everyone you follow, or 15 + // just where you follow the poster and the person they're replying to. Maybe this could be an 16 + // option in the config?? Currently, this is the "old" behaviour. 17 + // If we want the "new" version, we'll need to add it into hydrate_feed_posts... 18 + // <mia opinion>i like how it works currently on bsky</mia opinion> 19 + pub async fn get_timeline( 20 + State(state): State<GlobalState>, 21 + AtpAcceptLabelers(labelers): AtpAcceptLabelers, 22 + auth: AtpAuth, 23 + Query(query): Query<CursorQuery>, 24 + ) -> XrpcResult<Json<FeedRes>> { 25 + let mut conn = state.pool.get().await?; 26 + 27 + let did = auth.0.clone(); 28 + let hyd = StatefulHydrator::new(&state.dataloaders, &state.cdn, &labelers, Some(auth)); 29 + let limit = query.limit.unwrap_or(50).clamp(1, 100); 30 + 31 + let follows_query = schema::follows::table 32 + .select(schema::follows::subject) 33 + .filter(schema::follows::did.eq(&did)); 34 + 35 + let mut tl_query = schema::author_feeds::table 36 + .select(models::AuthorFeedItem::as_select()) 37 + .filter( 38 + schema::author_feeds::did 39 + .eq_any(follows_query) 40 + .or(schema::author_feeds::did.eq(&did)), 41 + ) 42 + .into_boxed(); 43 + 44 + if let Some(cursor) = datetime_cursor(query.cursor.as_ref()) { 45 + tl_query = tl_query.filter(schema::author_feeds::sort_at.lt(cursor)); 46 + } 47 + 48 + let results = tl_query 49 + .order(schema::author_feeds::sort_at.desc()) 50 + .limit(limit as i64) 51 + .load(&mut conn) 52 + .await?; 53 + 54 + let cursor = results 55 + .last() 56 + .map(|item| item.sort_at.timestamp_millis().to_string()); 57 + 58 + let raw_feed = results 59 + .into_iter() 60 + .filter_map(|item| match &*item.typ { 61 + "post" => Some(RawFeedItem::Post { 62 + uri: item.post, 63 + context: None, 64 + }), 65 + "repost" => Some(RawFeedItem::Repost { 66 + uri: item.uri, 67 + post: item.post, 68 + by: item.did, 69 + at: item.sort_at, 70 + context: None, 71 + }), 72 + _ => None, 73 + }) 74 + .collect::<Vec<_>>(); 75 + 76 + let feed = hyd.hydrate_feed_posts(raw_feed, false).await; 77 + 78 + Ok(Json(FeedRes { cursor, feed })) 79 + }
+1 -1
crates/parakeet/src/xrpc/app_bsky/mod.rs
··· 34 34 .route("/app.bsky.feed.getQuotes", get(feed::posts::get_quotes)) 35 35 .route("/app.bsky.feed.getRepostedBy", get(feed::posts::get_reposted_by)) 36 36 // TODO: app.bsky.feed.getSuggestedFeeds (recs) 37 - // TODO: app.bsky.feed.getTimeline (complicated) 37 + .route("/app.bsky.feed.getTimeline", get(feed::timeline::get_timeline)) 38 38 // TODO: app.bsky.feed.searchPosts (search) 39 39 .route("/app.bsky.graph.getActorStarterPacks", get(graph::starter_packs::get_actor_starter_packs)) 40 40 .route("/app.bsky.graph.getBlocks", get(graph::relations::get_blocks))

History

2 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
feat(parakeet): timelines
expand 0 comments
pull request successfully merged
1 commit
expand
feat(parakeet): timelines
expand 0 comments