From d252131e0da1ac51f34370c8f07c4a927c9991e1 Mon Sep 17 00:00:00 2001 From: nullishamy Date: Mon, 28 Apr 2025 21:06:17 +0100 Subject: [PATCH] feat: ! minor crimes ! timeline query --- ferri-server/src/endpoints/api/timeline.rs | 160 ++++++++++++--------- migrations/20250410182845_add_post.sql | 2 +- 2 files changed, 94 insertions(+), 68 deletions(-) diff --git a/ferri-server/src/endpoints/api/timeline.rs b/ferri-server/src/endpoints/api/timeline.rs index f6f7dcf..7748621 100644 --- a/ferri-server/src/endpoints/api/timeline.rs +++ b/ferri-server/src/endpoints/api/timeline.rs @@ -39,36 +39,60 @@ pub async fn home( config: &State, _user: AuthenticatedUser, ) -> Json> { - let posts = sqlx::query!( - r#" - SELECT p.id as "post_id", u.id as "user_id", p.content, p.uri as "post_uri", - u.username, u.display_name, u.actor_id, p.created_at, p.boosted_post_id - FROM post p - INNER JOIN user u on p.user_id = u.id - ORDER BY datetime(p.created_at) DESC + #[derive(sqlx::FromRow, Debug)] + struct Post { + is_boost_source: bool, + post_id: String, + user_id: String, + post_uri: String, + content: String, + created_at: String, + boosted_post_id: Option, + display_name: String, + username: String + } + + let posts = sqlx::query_as::<_, Post>( + r#" + WITH RECURSIVE get_home_timeline_with_boosts( + id, boosted_post_id, is_boost_source + ) AS + ( + SELECT p.id, p.boosted_post_id, 0 as is_boost_source + FROM post p + WHERE p.user_id IN ( + SELECT u.id + FROM follow f + INNER JOIN user u ON u.actor_id = f.followed_id + WHERE f.follower_id = $1 + ) + UNION + SELECT p.id, p.boosted_post_id, 1 as is_boost_source + FROM post p + JOIN get_home_timeline_with_boosts tl ON tl.boosted_post_id = p.id + ) + SELECT is_boost_source, p.id as "post_id", u.id as "user_id", + p.content, p.uri as "post_uri", u.username, u.display_name, + u.actor_id, p.created_at, p.boosted_post_id + FROM get_home_timeline_with_boosts + JOIN post p ON p.id = get_home_timeline_with_boosts.id + JOIN user u ON u.id = p.user_id; "# ) - .fetch_all(&mut **db) - .await - .unwrap(); + .bind("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9") + .fetch_all(&mut **db) + .await + .unwrap(); + + dbg!(&posts); let mut out = Vec::::new(); - for record in posts { + for record in posts.iter() { let mut boost: Option> = None; - if let Some(boosted_id) = record.boosted_post_id { - let record = sqlx::query!( - r#" - SELECT p.id as "post_id", u.id as "user_id", p.content, p.uri as "post_uri", - u.username, u.display_name, u.actor_id, p.created_at, p.boosted_post_id - FROM post p - INNER JOIN user u on p.user_id = u.id - WHERE p.id = ?1 - "#, boosted_id) - .fetch_one(&mut **db) - .await - .unwrap(); - + if let Some(ref boosted_id) = record.boosted_post_id { let user_uri = config.user_url(&record.user_id); + let record = posts.iter().find(|p| &p.post_id == boosted_id).unwrap(); + boost = Some(Box::new(TimelineStatus { id: record.post_id.clone(), created_at: record.created_at.clone(), @@ -111,49 +135,51 @@ pub async fn home( }, })) } - - let user_uri = config.user_web_url(&record.username); - out.push(TimelineStatus { - id: record.post_id.clone(), - created_at: record.created_at.clone(), - in_reply_to_id: None, - in_reply_to_account_id: None, - content: record.content.clone(), - visibility: "public".to_string(), - spoiler_text: "".to_string(), - sensitive: false, - uri: record.post_uri.clone(), - url: record.post_uri.clone(), - replies_count: 0, - reblogs_count: 0, - favourites_count: 0, - favourited: false, - reblogged: false, - reblog: boost, - muted: false, - bookmarked: false, - media_attachments: vec![], - account: CredentialAcount { - id: record.user_id.clone(), - username: record.username.clone(), - acct: record.username.clone(), - display_name: record.display_name.clone(), - locked: false, - bot: false, - created_at: "2025-04-10T22:12:09Z".to_string(), - attribution_domains: vec![], - note: "".to_string(), - url: user_uri, - avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(), - avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header: "https://ferri.amy.mov/assets/pfp.png".to_string(), - header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - followers_count: 1, - following_count: 1, - statuses_count: 1, - last_status_at: "2025-04-10T22:14:34Z".to_string(), - }, - }); + + if !record.is_boost_source { + let user_uri = config.user_web_url(&record.username); + out.push(TimelineStatus { + id: record.post_id.clone(), + created_at: record.created_at.clone(), + in_reply_to_id: None, + in_reply_to_account_id: None, + content: record.content.clone(), + visibility: "public".to_string(), + spoiler_text: "".to_string(), + sensitive: false, + uri: record.post_uri.clone(), + url: record.post_uri.clone(), + replies_count: 0, + reblogs_count: 0, + favourites_count: 0, + favourited: false, + reblogged: false, + reblog: boost, + muted: false, + bookmarked: false, + media_attachments: vec![], + account: CredentialAcount { + id: record.user_id.clone(), + username: record.username.clone(), + acct: record.username.clone(), + display_name: record.display_name.clone(), + locked: false, + bot: false, + created_at: "2025-04-10T22:12:09Z".to_string(), + attribution_domains: vec![], + note: "".to_string(), + url: user_uri, + avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(), + avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), + header: "https://ferri.amy.mov/assets/pfp.png".to_string(), + header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), + followers_count: 1, + following_count: 1, + statuses_count: 1, + last_status_at: "2025-04-10T22:14:34Z".to_string(), + }, + }); + } } Json(out) diff --git a/migrations/20250410182845_add_post.sql b/migrations/20250410182845_add_post.sql index 04c66bd..95e2a2a 100644 --- a/migrations/20250410182845_add_post.sql +++ b/migrations/20250410182845_add_post.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS post ( id TEXT PRIMARY KEY NOT NULL, - uri TEXT NOT NULL, + uri TEXT NOT NULL UNIQUE, user_id TEXT NOT NULL, content TEXT NOT NULL, created_at TEXT NOT NULL,