Noreposts Feed
at main 114 lines 3.3 kB view raw
1use anyhow::Result; 2use std::sync::Arc; 3use tracing::warn; 4 5use crate::{ 6 database::Database, 7 types::{FeedSkeletonResponse, SkeletonFeedPost}, 8}; 9 10pub struct FollowingNoRepostsFeed { 11 db: Arc<Database>, 12} 13 14impl FollowingNoRepostsFeed { 15 pub fn new(db: Arc<Database>) -> Self { 16 Self { db } 17 } 18 19 pub async fn generate_feed( 20 &self, 21 requester_did: Option<String>, 22 limit: Option<i32>, 23 cursor: Option<String>, 24 ) -> Result<FeedSkeletonResponse> { 25 // Require authentication for this feed since it's personalized 26 let follower_did = match requester_did { 27 Some(did) => did, 28 None => { 29 warn!("Unauthenticated request to following feed"); 30 return Ok(FeedSkeletonResponse { 31 cursor: None, 32 feed: vec![], 33 }); 34 } 35 }; 36 37 let limit = limit.unwrap_or(50).min(100); // Cap at 100 items 38 39 // Get posts from accounts the user follows 40 let posts = self 41 .db 42 .get_following_posts(&follower_did, limit, cursor.as_deref()) 43 .await?; 44 45 tracing::info!( 46 "Feed generated for {}: found {} posts from followed accounts", 47 follower_did, 48 posts.len() 49 ); 50 51 let feed_posts: Vec<SkeletonFeedPost> = posts 52 .iter() 53 .map(|post| SkeletonFeedPost { 54 post: post.uri.clone(), 55 }) 56 .collect(); 57 58 // Generate cursor for pagination (use created_at for chronological order) 59 let cursor = posts.last().map(|post| post.created_at.to_rfc3339()); 60 61 Ok(FeedSkeletonResponse { 62 cursor, 63 feed: feed_posts, 64 }) 65 } 66} 67 68#[cfg(test)] 69mod tests { 70 use super::*; 71 use crate::types::{Follow, Post}; 72 use chrono::Utc; 73 74 #[tokio::test] 75 async fn test_feed_generation() -> Result<()> { 76 let db = Arc::new(Database::new(":memory:").await?); 77 db.migrate().await?; 78 79 // Create test data 80 let follower_did = "did:example:alice"; 81 let target_did = "did:example:bob"; 82 83 // Insert follow relationship 84 let follow = Follow { 85 uri: format!("at://{}/app.bsky.graph.follow/test", follower_did), 86 follower_did: follower_did.to_string(), 87 target_did: target_did.to_string(), 88 created_at: Utc::now(), 89 indexed_at: Utc::now(), 90 }; 91 db.insert_follow(&follow).await?; 92 93 // Insert post from followed user 94 let post = Post { 95 uri: format!("at://{}/app.bsky.feed.post/test", target_did), 96 cid: "test-cid".to_string(), 97 author_did: target_did.to_string(), 98 text: "Hello world!".to_string(), 99 created_at: Utc::now(), 100 indexed_at: Utc::now(), 101 }; 102 db.insert_post(&post).await?; 103 104 let feed_algorithm = FollowingNoRepostsFeed::new(Arc::clone(&db)); 105 let response = feed_algorithm 106 .generate_feed(Some(follower_did.to_string()), Some(10), None) 107 .await?; 108 109 assert_eq!(response.feed.len(), 1); 110 assert_eq!(response.feed[0].post, post.uri); 111 112 Ok(()) 113 } 114}