Parakeet is a Rust-based Bluesky AppView aiming to implement most of the functionality required to support the Bluesky client

feat(parakeet): getBlocks and getListBlocks

Changed files
+91 -3
parakeet
src
xrpc
app_bsky
+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))