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.

fix(parakeet): properly remove inactive accounts

Mia 801ce118 50f2fff3

+62 -6
+15
parakeet/src/db.rs
··· 1 + use diesel::prelude::*; 2 + use diesel_async::{AsyncPgConnection, RunQueryDsl}; 3 + use parakeet_db::{schema, types}; 4 + 5 + pub async fn get_actor_status( 6 + conn: &mut AsyncPgConnection, 7 + did: &str, 8 + ) -> QueryResult<Option<types::ActorStatus>> { 9 + schema::actors::table 10 + .select(schema::actors::status) 11 + .find(did) 12 + .get_result(conn) 13 + .await 14 + .optional() 15 + }
+5 -1
parakeet/src/loaders.rs
··· 91 91 schema::chat_decls::allow_incoming.nullable(), 92 92 schema::labelers::cid.nullable(), 93 93 )) 94 - .filter(schema::actors::did.eq_any(keys)) 94 + .filter( 95 + schema::actors::did 96 + .eq_any(keys) 97 + .and(schema::actors::status.eq("active")), 98 + ) 95 99 .load::<( 96 100 String, 97 101 Option<String>,
+1
parakeet/src/main.rs
··· 6 6 use tower_http::trace::TraceLayer; 7 7 8 8 mod config; 9 + mod db; 9 10 mod hydration; 10 11 mod loaders; 11 12 mod xrpc;
+4 -1
parakeet/src/xrpc/app_bsky/actor.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::{get_actor_did, get_actor_dids}; 4 + use crate::xrpc::{check_actor_status, get_actor_did, get_actor_dids}; 5 5 use crate::GlobalState; 6 6 use axum::extract::{Query, State}; 7 7 use axum::Json; ··· 23 23 let hyd = StatefulHydrator::new(&state.dataloaders, &labelers, maybe_auth); 24 24 25 25 let did = get_actor_did(&state.dataloaders, query.actor).await?; 26 + 27 + let mut conn = state.pool.get().await?; 28 + check_actor_status(&mut conn, &did).await?; 26 29 27 30 let maybe_profile = hyd.hydrate_profile_detailed(did).await; 28 31
+3 -1
parakeet/src/xrpc/app_bsky/feed/feedgen.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::{check_actor_status, datetime_cursor, get_actor_did, ActorWithCursorQuery}; 5 5 use crate::GlobalState; 6 6 use axum::extract::{Query, State}; 7 7 use axum::Json; ··· 29 29 let hyd = StatefulHydrator::new(&state.dataloaders, &labelers, maybe_auth); 30 30 31 31 let did = get_actor_did(&state.dataloaders, query.actor).await?; 32 + 33 + check_actor_status(&mut conn, &did).await?; 32 34 33 35 let limit = query.limit.unwrap_or(50).clamp(1, 100); 34 36
+5 -1
parakeet/src/xrpc/app_bsky/feed/posts.rs
··· 2 2 use crate::xrpc::app_bsky::graph::lists::ListWithCursorQuery; 3 3 use crate::xrpc::error::{Error, XrpcResult}; 4 4 use crate::xrpc::extract::{AtpAcceptLabelers, AtpAuth}; 5 - use crate::xrpc::{datetime_cursor, get_actor_did, normalise_at_uri}; 5 + use crate::xrpc::{check_actor_status, datetime_cursor, get_actor_did, normalise_at_uri}; 6 6 use crate::GlobalState; 7 7 use axum::extract::{Query, State}; 8 8 use axum::Json; ··· 65 65 66 66 let did = get_actor_did(&state.dataloaders, query.actor.clone()).await?; 67 67 68 + check_actor_status(&mut conn, &did).await?; 69 + 68 70 let limit = query.limit.unwrap_or(50).clamp(1, 100); 69 71 70 72 let mut posts_query = schema::posts::table ··· 125 127 Ok(Json(FeedRes { cursor, feed })) 126 128 } 127 129 130 + // While fixing inactive accounts, i noticed that you can still call this endpoint for a list on an 131 + // inactive account on the public appview - idk if this is correct behaviour, but we're matching it 128 132 pub async fn get_list_feed( 129 133 State(state): State<GlobalState>, 130 134 AtpAcceptLabelers(labelers): AtpAcceptLabelers,
+3 -1
parakeet/src/xrpc/app_bsky/graph/lists.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::{check_actor_status, datetime_cursor, get_actor_did, ActorWithCursorQuery}; 5 5 use crate::GlobalState; 6 6 use axum::extract::{Query, State}; 7 7 use axum::Json; ··· 35 35 let hyd = StatefulHydrator::new(&state.dataloaders, &labelers, maybe_auth); 36 36 37 37 let did = get_actor_did(&state.dataloaders, query.actor).await?; 38 + 39 + check_actor_status(&mut conn, &did).await?; 38 40 39 41 let limit = query.limit.unwrap_or(50).clamp(1, 100); 40 42
+3 -1
parakeet/src/xrpc/app_bsky/graph/starter_packs.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::{check_actor_status, datetime_cursor, get_actor_did, ActorWithCursorQuery}; 5 5 use crate::GlobalState; 6 6 use axum::extract::{Query, State}; 7 7 use axum::Json; ··· 30 30 let hyd = StatefulHydrator::new(&state.dataloaders, &labelers, maybe_auth); 31 31 32 32 let subj_did = get_actor_did(&state.dataloaders, query.actor).await?; 33 + 34 + check_actor_status(&mut conn, &subj_did).await?; 33 35 34 36 let limit = query.limit.unwrap_or(50).clamp(1, 100); 35 37
+23
parakeet/src/xrpc/mod.rs
··· 1 1 use crate::loaders::Dataloaders; 2 + use crate::xrpc::error::Error; 3 + use axum::http::StatusCode; 2 4 use axum::Router; 3 5 use serde::Deserialize; 4 6 use std::str::FromStr; ··· 68 70 } 69 71 70 72 Ok(out) 73 + } 74 + 75 + async fn check_actor_status( 76 + conn: &mut diesel_async::AsyncPgConnection, 77 + did: &str, 78 + ) -> error::XrpcResult<()> { 79 + match crate::db::get_actor_status(conn, did).await? { 80 + Some(parakeet_db::types::ActorStatus::Active) => Ok(()), 81 + Some(parakeet_db::types::ActorStatus::Takendown) 82 + | Some(parakeet_db::types::ActorStatus::Suspended) => Err(Error::new( 83 + StatusCode::NOT_FOUND, 84 + "AccountTakedown", 85 + Some("Account has been suspended".to_string()), 86 + )), 87 + Some(parakeet_db::types::ActorStatus::Deactivated) => Err(Error::new( 88 + StatusCode::NOT_FOUND, 89 + "AccountDeactivated", 90 + Some("Account is deactivated".to_string()), 91 + )), 92 + Some(parakeet_db::types::ActorStatus::Deleted) | None => Err(Error::not_found()), 93 + } 71 94 } 72 95 73 96 #[derive(Debug, Deserialize)]