From d9def75b32573162c7362ead85358ac879eb014d Mon Sep 17 00:00:00 2001 From: silverpill Date: Sun, 21 Aug 2022 21:15:30 +0000 Subject: [PATCH] Create post_link table to store links between posts --- migrations/V0027__post_link.sql | 5 ++ migrations/schema.sql | 6 ++ src/activitypub/handlers/create_note.rs | 1 + src/mastodon_api/statuses/types.rs | 1 + src/models/notifications/queries.rs | 5 +- src/models/notifications/types.rs | 2 + src/models/posts/queries.rs | 101 ++++++++++++++++++++---- src/models/posts/types.rs | 20 ++++- 8 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 migrations/V0027__post_link.sql diff --git a/migrations/V0027__post_link.sql b/migrations/V0027__post_link.sql new file mode 100644 index 0000000..255f655 --- /dev/null +++ b/migrations/V0027__post_link.sql @@ -0,0 +1,5 @@ +CREATE TABLE post_link ( + source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE, + target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE, + PRIMARY KEY (source_id, target_id) +); diff --git a/migrations/schema.sql b/migrations/schema.sql index ec9ad0d..557ceae 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -112,6 +112,12 @@ CREATE TABLE post_tag ( PRIMARY KEY (post_id, tag_id) ); +CREATE TABLE post_link ( + source_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE, + target_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE, + PRIMARY KEY (source_id, target_id) +); + CREATE TABLE notification ( id SERIAL PRIMARY KEY, sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE, diff --git a/src/activitypub/handlers/create_note.rs b/src/activitypub/handlers/create_note.rs index 83d76e4..a19f031 100644 --- a/src/activitypub/handlers/create_note.rs +++ b/src/activitypub/handlers/create_note.rs @@ -321,6 +321,7 @@ pub async fn handle_note( attachments: attachments, mentions: mentions, tags: tags, + links: vec![], object_id: Some(object.id), created_at: object.published, }; diff --git a/src/mastodon_api/statuses/types.rs b/src/mastodon_api/statuses/types.rs index 577a20e..6d27169 100644 --- a/src/mastodon_api/statuses/types.rs +++ b/src/mastodon_api/statuses/types.rs @@ -163,6 +163,7 @@ impl TryFrom for PostCreateData { attachments: value.media_ids.unwrap_or(vec![]), mentions: value.mentions.unwrap_or(vec![]), tags: vec![], + links: vec![], object_id: None, created_at: None, }; diff --git a/src/models/notifications/queries.rs b/src/models/notifications/queries.rs index 56285db..5c51a53 100644 --- a/src/models/notifications/queries.rs +++ b/src/models/notifications/queries.rs @@ -7,6 +7,7 @@ use crate::errors::DatabaseError; use crate::models::posts::helpers::add_user_actions; use crate::models::posts::queries::{ RELATED_ATTACHMENTS, + RELATED_LINKS, RELATED_MENTIONS, RELATED_TAGS, }; @@ -127,7 +128,8 @@ pub async fn get_notifications( notification, sender, post, post_author, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM notification JOIN actor_profile AS sender ON notification.sender_id = sender.id @@ -144,6 +146,7 @@ pub async fn get_notifications( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, ); let rows = db_client.query( statement.as_str(), diff --git a/src/models/notifications/types.rs b/src/models/notifications/types.rs index c16fe21..6483134 100644 --- a/src/models/notifications/types.rs +++ b/src/models/notifications/types.rs @@ -97,12 +97,14 @@ impl TryFrom<&Row> for Notification { let db_attachments: Vec = row.try_get("attachments")?; let db_mentions: Vec = row.try_get("mentions")?; let db_tags: Vec = row.try_get("tags")?; + let db_links: Vec = row.try_get("links")?; let post = Post::new( db_post, db_post_author, db_attachments, db_mentions, db_tags, + db_links, )?; Some(post) }, diff --git a/src/models/posts/queries.rs b/src/models/posts/queries.rs index 202f87c..99e2a16 100644 --- a/src/models/posts/queries.rs +++ b/src/models/posts/queries.rs @@ -96,7 +96,7 @@ pub async fn create_post( if attachments_rows.len() != data.attachments.len() { // Some attachments were not found return Err(DatabaseError::NotFound("attachment")); - } + }; let db_attachments: Vec = attachments_rows.iter() .map(|row| row.try_get("media_attachment")) .collect::>()?; @@ -104,7 +104,7 @@ pub async fn create_post( let mentions_rows = transaction.query( " INSERT INTO mention (post_id, profile_id) - SELECT $1, unnest($2::uuid[]) + SELECT $1, actor_profile.id FROM actor_profile WHERE id = ANY($2) RETURNING ( SELECT actor_profile FROM actor_profile WHERE actor_profile.id = profile_id @@ -142,6 +142,21 @@ pub async fn create_post( let db_tags: Vec = tags_rows.iter() .map(|row| row.try_get("tag_name")) .collect::>()?; + // Create links + let links_rows = transaction.query( + " + INSERT INTO post_link (source_id, target_id) + SELECT $1, post.id FROM post WHERE id = ANY($2) + RETURNING target_id + ", + &[&db_post.id, &data.links], + ).await?; + if links_rows.len() != data.links.len() { + return Err(DatabaseError::NotFound("post")); + }; + let db_links: Vec = links_rows.iter() + .map(|row| row.try_get("target_id")) + .collect::>()?; // Update counters let author = update_post_count(&transaction, &db_post.author_id, 1).await?; let mut notified_users = vec![]; @@ -194,7 +209,14 @@ pub async fn create_post( }; transaction.commit().await?; - let post = Post::new(db_post, author, db_attachments, db_mentions, db_tags)?; + let post = Post::new( + db_post, + author, + db_attachments, + db_mentions, + db_tags, + db_links, + )?; Ok(post) } @@ -219,6 +241,12 @@ pub const RELATED_TAGS: &str = WHERE post_tag.post_id = post.id ) AS tags"; +pub const RELATED_LINKS: &str = + "ARRAY( + SELECT post_link.target_id FROM post_link + WHERE post_link.source_id = post.id + ) AS links"; + fn build_visibility_filter() -> String { format!( "( @@ -266,7 +294,8 @@ pub async fn get_home_timeline( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE @@ -331,6 +360,7 @@ pub async fn get_home_timeline( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, relationship_follow=i16::from(&RelationshipType::Follow), relationship_subscription=i16::from(&RelationshipType::Subscription), relationship_hide_reposts=i16::from(&RelationshipType::HideReposts), @@ -362,7 +392,8 @@ pub async fn get_local_timeline( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE @@ -375,6 +406,7 @@ pub async fn get_local_timeline( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, visibility_public=i16::from(&Visibility::Public), ); let query = query!( @@ -400,7 +432,8 @@ pub async fn get_posts( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE post.id = ANY($1) @@ -408,6 +441,7 @@ pub async fn get_posts( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, ); let rows = db_client.query( statement.as_str(), @@ -446,7 +480,8 @@ pub async fn get_posts_by_author( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE {condition} @@ -456,6 +491,7 @@ pub async fn get_posts_by_author( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, condition=condition, ); let query = query!( @@ -486,7 +522,8 @@ pub async fn get_posts_by_tag( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE @@ -502,6 +539,7 @@ pub async fn get_posts_by_tag( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, visibility_filter=build_visibility_filter(), ); let query = query!( @@ -528,7 +566,8 @@ pub async fn get_post_by_id( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE post.id = $1 @@ -536,6 +575,7 @@ pub async fn get_post_by_id( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, ); let maybe_row = db_client.query_opt( statement.as_str(), @@ -579,7 +619,8 @@ pub async fn get_thread( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN thread ON post.id = thread.id JOIN actor_profile ON post.author_id = actor_profile.id @@ -589,6 +630,7 @@ pub async fn get_thread( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, visibility_filter=build_visibility_filter(), ); let query = query!( @@ -616,7 +658,8 @@ pub async fn get_post_by_object_id( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE post.object_id = $1 @@ -624,6 +667,7 @@ pub async fn get_post_by_object_id( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, ); let maybe_row = db_client.query_opt( statement.as_str(), @@ -644,7 +688,8 @@ pub async fn get_post_by_ipfs_cid( post, actor_profile, {related_attachments}, {related_mentions}, - {related_tags} + {related_tags}, + {related_links} FROM post JOIN actor_profile ON post.author_id = actor_profile.id WHERE post.ipfs_cid = $1 @@ -652,6 +697,7 @@ pub async fn get_post_by_ipfs_cid( related_attachments=RELATED_ATTACHMENTS, related_mentions=RELATED_MENTIONS, related_tags=RELATED_TAGS, + related_links=RELATED_LINKS, ); let result = db_client.query_opt( statement.as_str(), @@ -1086,21 +1132,44 @@ mod tests { #[serial] async fn test_create_post() { let db_client = &mut create_test_database().await; - let profile_data = ProfileCreateData { + let author_data = ProfileCreateData { username: "test".to_string(), ..Default::default() }; - let profile = create_profile(db_client, profile_data).await.unwrap(); + let author = create_profile(db_client, author_data).await.unwrap(); let post_data = PostCreateData { content: "test post".to_string(), ..Default::default() }; - let post = create_post(db_client, &profile.id, post_data).await.unwrap(); + let post = create_post(db_client, &author.id, post_data).await.unwrap(); assert_eq!(post.content, "test post"); - assert_eq!(post.author.id, profile.id); + assert_eq!(post.author.id, author.id); + assert_eq!(post.attachments.is_empty(), true); + assert_eq!(post.mentions.is_empty(), true); + assert_eq!(post.tags.is_empty(), true); + assert_eq!(post.links.is_empty(), true); assert_eq!(post.updated_at, None); } + #[tokio::test] + #[serial] + async fn test_create_post_with_link() { + let db_client = &mut create_test_database().await; + let author_data = ProfileCreateData { + username: "test".to_string(), + ..Default::default() + }; + let author = create_profile(db_client, author_data).await.unwrap(); + let post_data_1 = PostCreateData::default(); + let post_1 = create_post(db_client, &author.id, post_data_1).await.unwrap(); + let post_data_2 = PostCreateData { + links: vec![post_1.id], + ..Default::default() + }; + let post_2 = create_post(db_client, &author.id, post_data_2).await.unwrap(); + assert_eq!(post_2.links, vec![post_1.id]); + } + #[tokio::test] #[serial] async fn test_update_post() { diff --git a/src/models/posts/types.rs b/src/models/posts/types.rs index 7e5fc0b..49daefb 100644 --- a/src/models/posts/types.rs +++ b/src/models/posts/types.rs @@ -94,6 +94,7 @@ pub struct Post { pub attachments: Vec, pub mentions: Vec, pub tags: Vec, + pub links: Vec, pub object_id: Option, pub ipfs_cid: Option, pub token_id: Option, @@ -115,6 +116,7 @@ impl Post { db_attachments: Vec, db_mentions: Vec, db_tags: Vec, + db_links: Vec, ) -> Result { // Consistency checks if db_post.author_id != db_author.id { @@ -131,7 +133,8 @@ impl Post { db_post.token_tx_id.is_some() || !db_attachments.is_empty() || !db_mentions.is_empty() || - !db_tags.is_empty() + !db_tags.is_empty() || + !db_links.is_empty() ) { return Err(ConversionError); }; @@ -148,6 +151,7 @@ impl Post { attachments: db_attachments, mentions: db_mentions, tags: db_tags, + links: db_links, object_id: db_post.object_id, ipfs_cid: db_post.ipfs_cid, token_id: db_post.token_id, @@ -189,6 +193,7 @@ impl Default for Post { attachments: vec![], mentions: vec![], tags: vec![], + links: vec![], object_id: None, ipfs_cid: None, token_id: None, @@ -212,7 +217,15 @@ impl TryFrom<&Row> for Post { let db_attachments: Vec = row.try_get("attachments")?; let db_mentions: Vec = row.try_get("mentions")?; let db_tags: Vec = row.try_get("tags")?; - let post = Self::new(db_post, db_profile, db_attachments, db_mentions, db_tags)?; + let db_links: Vec = row.try_get("links")?; + let post = Self::new( + db_post, + db_profile, + db_attachments, + db_mentions, + db_tags, + db_links, + )?; Ok(post) } } @@ -226,6 +239,7 @@ pub struct PostCreateData { pub attachments: Vec, pub mentions: Vec, pub tags: Vec, + pub links: Vec, pub object_id: Option, pub created_at: Option>, } @@ -267,6 +281,7 @@ mod tests { attachments: vec![], mentions: vec![], tags: vec![], + links: vec![], object_id: None, created_at: None, }; @@ -283,6 +298,7 @@ mod tests { attachments: vec![], mentions: vec![], tags: vec![], + links: vec![], object_id: None, created_at: None, };