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
66
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(parakeet): getBlocks and getListBlocks

Mia f54327a5 0832a3f9

+91 -3
+42
parakeet/src/xrpc/app_bsky/graph/lists.rs
··· 181 181 lists: mutes, 182 182 })) 183 183 } 184 + 185 + pub async fn get_list_blocks( 186 + State(state): State<GlobalState>, 187 + AtpAcceptLabelers(labelers): AtpAcceptLabelers, 188 + auth: AtpAuth, 189 + Query(query): Query<CursorQuery>, 190 + ) -> XrpcResult<Json<GetListMutesRes>> { 191 + let mut conn = state.pool.get().await?; 192 + let did = auth.0.clone(); 193 + let hyd = StatefulHydrator::new(&state.dataloaders, &state.cdn, &labelers, Some(auth)); 194 + 195 + let limit = query.limit.unwrap_or(50).clamp(1, 100); 196 + 197 + let mut blocks_query = schema::list_blocks::table 198 + .select((schema::list_blocks::created_at, schema::list_blocks::list_uri)) 199 + .filter(schema::list_blocks::did.eq(did)) 200 + .into_boxed(); 201 + 202 + if let Some(cursor) = datetime_cursor(query.cursor.as_ref()) { 203 + blocks_query = blocks_query.filter(schema::list_blocks::created_at.lt(cursor)); 204 + } 205 + 206 + let results = blocks_query 207 + .order(schema::list_blocks::created_at.desc()) 208 + .limit(limit as i64) 209 + .load::<(chrono::DateTime<chrono::Utc>, String)>(&mut conn) 210 + .await?; 211 + 212 + let cursor = results 213 + .last() 214 + .map(|(last, _)| last.timestamp_millis().to_string()); 215 + 216 + let uris = results.iter().map(|(_, uri)| uri.clone()).collect(); 217 + 218 + let lists = hyd.hydrate_lists(uris).await; 219 + let lists = lists.into_values().collect::<Vec<_>>(); 220 + 221 + Ok(Json(GetListMutesRes { 222 + cursor, 223 + lists, 224 + })) 225 + }
+47 -1
parakeet/src/xrpc/app_bsky/graph/relations.rs
··· 1 1 use crate::hydration::StatefulHydrator; 2 2 use crate::xrpc::error::{Error, XrpcResult}; 3 3 use crate::xrpc::extract::{AtpAcceptLabelers, AtpAuth}; 4 - use crate::xrpc::{datetime_cursor, get_actor_did, ActorWithCursorQuery}; 4 + use crate::xrpc::{datetime_cursor, get_actor_did, ActorWithCursorQuery, CursorQuery}; 5 5 use crate::GlobalState; 6 6 use axum::extract::{Query, State}; 7 7 use axum::Json; ··· 10 10 use lexica::app_bsky::actor::ProfileView; 11 11 use parakeet_db::schema; 12 12 use serde::Serialize; 13 + 14 + #[derive(Debug, Serialize)] 15 + pub struct GetBlocksRes { 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + cursor: Option<String>, 18 + blocks: Vec<ProfileView>, 19 + } 20 + 21 + pub async fn get_blocks( 22 + State(state): State<GlobalState>, 23 + AtpAcceptLabelers(labelers): AtpAcceptLabelers, 24 + auth: AtpAuth, 25 + Query(query): Query<CursorQuery>, 26 + ) -> XrpcResult<Json<GetBlocksRes>> { 27 + let mut conn = state.pool.get().await?; 28 + let did = auth.0.clone(); 29 + let hyd = StatefulHydrator::new(&state.dataloaders, &state.cdn, &labelers, Some(auth)); 30 + 31 + let limit = query.limit.unwrap_or(50).clamp(1, 100); 32 + 33 + let mut blocked_query = schema::blocks::table 34 + .select((schema::blocks::created_at, schema::blocks::subject)) 35 + .filter(schema::blocks::did.eq(did)) 36 + .into_boxed(); 37 + 38 + if let Some(cursor) = datetime_cursor(query.cursor.as_ref()) { 39 + blocked_query = blocked_query.filter(schema::blocks::created_at.lt(cursor)); 40 + } 41 + 42 + let results = blocked_query 43 + .order(schema::blocks::created_at.desc()) 44 + .limit(limit as i64) 45 + .load::<(chrono::DateTime<chrono::Utc>, String)>(&mut conn) 46 + .await?; 47 + 48 + let cursor = results 49 + .last() 50 + .map(|(last, _)| last.timestamp_millis().to_string()); 51 + 52 + let dids = results.iter().map(|(_, did)| did.clone()).collect(); 53 + 54 + let profiles = hyd.hydrate_profiles(dids).await; 55 + let blocks = profiles.into_values().collect::<Vec<_>>(); 56 + 57 + Ok(Json(GetBlocksRes { cursor, blocks })) 58 + } 13 59 14 60 #[derive(Debug, Serialize)] 15 61 pub struct AppBskyGraphGetFollowersRes {
+2 -2
parakeet/src/xrpc/app_bsky/mod.rs
··· 30 30 // TODO: app.bsky.feed.getTimeline (complicated) 31 31 // TODO: app.bsky.feed.searchPosts (search) 32 32 .route("/app.bsky.graph.getActorStarterPacks", get(graph::starter_packs::get_actor_starter_packs)) 33 - // TODO: app.bsky.graph.getBlocks 33 + .route("/app.bsky.graph.getBlocks", get(graph::relations::get_blocks)) 34 34 .route("/app.bsky.graph.getFollowers", get(graph::relations::get_followers)) 35 35 .route("/app.bsky.graph.getFollows", get(graph::relations::get_follows)) 36 36 // TODO: app.bsky.graph.getKnownFollowers 37 37 .route("/app.bsky.graph.getList", get(graph::lists::get_list)) 38 - // TODO: app.bsky.graph.getListBlocks 38 + .route("/app.bsky.graph.getListBlocks", get(graph::lists::get_list_blocks)) 39 39 .route("/app.bsky.graph.getListMutes", get(graph::lists::get_list_mutes)) 40 40 .route("/app.bsky.graph.getLists", get(graph::lists::get_lists)) 41 41 .route("/app.bsky.graph.getMutes", get(graph::mutes::get_mutes))