Create post_link table to store links between posts

This commit is contained in:
silverpill 2022-08-21 21:15:30 +00:00
parent fcf7db97cb
commit d9def75b32
8 changed files with 122 additions and 19 deletions

View file

@ -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)
);

View file

@ -112,6 +112,12 @@ CREATE TABLE post_tag (
PRIMARY KEY (post_id, tag_id) 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 ( CREATE TABLE notification (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE, sender_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,

View file

@ -321,6 +321,7 @@ pub async fn handle_note(
attachments: attachments, attachments: attachments,
mentions: mentions, mentions: mentions,
tags: tags, tags: tags,
links: vec![],
object_id: Some(object.id), object_id: Some(object.id),
created_at: object.published, created_at: object.published,
}; };

View file

@ -163,6 +163,7 @@ impl TryFrom<StatusData> for PostCreateData {
attachments: value.media_ids.unwrap_or(vec![]), attachments: value.media_ids.unwrap_or(vec![]),
mentions: value.mentions.unwrap_or(vec![]), mentions: value.mentions.unwrap_or(vec![]),
tags: vec![], tags: vec![],
links: vec![],
object_id: None, object_id: None,
created_at: None, created_at: None,
}; };

View file

@ -7,6 +7,7 @@ use crate::errors::DatabaseError;
use crate::models::posts::helpers::add_user_actions; use crate::models::posts::helpers::add_user_actions;
use crate::models::posts::queries::{ use crate::models::posts::queries::{
RELATED_ATTACHMENTS, RELATED_ATTACHMENTS,
RELATED_LINKS,
RELATED_MENTIONS, RELATED_MENTIONS,
RELATED_TAGS, RELATED_TAGS,
}; };
@ -127,7 +128,8 @@ pub async fn get_notifications(
notification, sender, post, post_author, notification, sender, post, post_author,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM notification FROM notification
JOIN actor_profile AS sender JOIN actor_profile AS sender
ON notification.sender_id = sender.id ON notification.sender_id = sender.id
@ -144,6 +146,7 @@ pub async fn get_notifications(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
); );
let rows = db_client.query( let rows = db_client.query(
statement.as_str(), statement.as_str(),

View file

@ -97,12 +97,14 @@ impl TryFrom<&Row> for Notification {
let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?; let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?;
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?; let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
let db_tags: Vec<String> = row.try_get("tags")?; let db_tags: Vec<String> = row.try_get("tags")?;
let db_links: Vec<Uuid> = row.try_get("links")?;
let post = Post::new( let post = Post::new(
db_post, db_post,
db_post_author, db_post_author,
db_attachments, db_attachments,
db_mentions, db_mentions,
db_tags, db_tags,
db_links,
)?; )?;
Some(post) Some(post)
}, },

View file

@ -96,7 +96,7 @@ pub async fn create_post(
if attachments_rows.len() != data.attachments.len() { if attachments_rows.len() != data.attachments.len() {
// Some attachments were not found // Some attachments were not found
return Err(DatabaseError::NotFound("attachment")); return Err(DatabaseError::NotFound("attachment"));
} };
let db_attachments: Vec<DbMediaAttachment> = attachments_rows.iter() let db_attachments: Vec<DbMediaAttachment> = attachments_rows.iter()
.map(|row| row.try_get("media_attachment")) .map(|row| row.try_get("media_attachment"))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
@ -104,7 +104,7 @@ pub async fn create_post(
let mentions_rows = transaction.query( let mentions_rows = transaction.query(
" "
INSERT INTO mention (post_id, profile_id) 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 ( RETURNING (
SELECT actor_profile FROM actor_profile SELECT actor_profile FROM actor_profile
WHERE actor_profile.id = profile_id WHERE actor_profile.id = profile_id
@ -142,6 +142,21 @@ pub async fn create_post(
let db_tags: Vec<String> = tags_rows.iter() let db_tags: Vec<String> = tags_rows.iter()
.map(|row| row.try_get("tag_name")) .map(|row| row.try_get("tag_name"))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// 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<Uuid> = links_rows.iter()
.map(|row| row.try_get("target_id"))
.collect::<Result<_, _>>()?;
// Update counters // Update counters
let author = update_post_count(&transaction, &db_post.author_id, 1).await?; let author = update_post_count(&transaction, &db_post.author_id, 1).await?;
let mut notified_users = vec![]; let mut notified_users = vec![];
@ -194,7 +209,14 @@ pub async fn create_post(
}; };
transaction.commit().await?; 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) Ok(post)
} }
@ -219,6 +241,12 @@ pub const RELATED_TAGS: &str =
WHERE post_tag.post_id = post.id WHERE post_tag.post_id = post.id
) AS tags"; ) 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 { fn build_visibility_filter() -> String {
format!( format!(
"( "(
@ -266,7 +294,8 @@ pub async fn get_home_timeline(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -331,6 +360,7 @@ pub async fn get_home_timeline(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
relationship_follow=i16::from(&RelationshipType::Follow), relationship_follow=i16::from(&RelationshipType::Follow),
relationship_subscription=i16::from(&RelationshipType::Subscription), relationship_subscription=i16::from(&RelationshipType::Subscription),
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts), relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
@ -362,7 +392,8 @@ pub async fn get_local_timeline(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -375,6 +406,7 @@ pub async fn get_local_timeline(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
visibility_public=i16::from(&Visibility::Public), visibility_public=i16::from(&Visibility::Public),
); );
let query = query!( let query = query!(
@ -400,7 +432,8 @@ pub async fn get_posts(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.id = ANY($1) WHERE post.id = ANY($1)
@ -408,6 +441,7 @@ pub async fn get_posts(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
); );
let rows = db_client.query( let rows = db_client.query(
statement.as_str(), statement.as_str(),
@ -446,7 +480,8 @@ pub async fn get_posts_by_author(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE {condition} WHERE {condition}
@ -456,6 +491,7 @@ pub async fn get_posts_by_author(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
condition=condition, condition=condition,
); );
let query = query!( let query = query!(
@ -486,7 +522,8 @@ pub async fn get_posts_by_tag(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE WHERE
@ -502,6 +539,7 @@ pub async fn get_posts_by_tag(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
visibility_filter=build_visibility_filter(), visibility_filter=build_visibility_filter(),
); );
let query = query!( let query = query!(
@ -528,7 +566,8 @@ pub async fn get_post_by_id(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.id = $1 WHERE post.id = $1
@ -536,6 +575,7 @@ pub async fn get_post_by_id(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
); );
let maybe_row = db_client.query_opt( let maybe_row = db_client.query_opt(
statement.as_str(), statement.as_str(),
@ -579,7 +619,8 @@ pub async fn get_thread(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN thread ON post.id = thread.id JOIN thread ON post.id = thread.id
JOIN actor_profile ON post.author_id = actor_profile.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_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
visibility_filter=build_visibility_filter(), visibility_filter=build_visibility_filter(),
); );
let query = query!( let query = query!(
@ -616,7 +658,8 @@ pub async fn get_post_by_object_id(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.object_id = $1 WHERE post.object_id = $1
@ -624,6 +667,7 @@ pub async fn get_post_by_object_id(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
); );
let maybe_row = db_client.query_opt( let maybe_row = db_client.query_opt(
statement.as_str(), statement.as_str(),
@ -644,7 +688,8 @@ pub async fn get_post_by_ipfs_cid(
post, actor_profile, post, actor_profile,
{related_attachments}, {related_attachments},
{related_mentions}, {related_mentions},
{related_tags} {related_tags},
{related_links}
FROM post FROM post
JOIN actor_profile ON post.author_id = actor_profile.id JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.ipfs_cid = $1 WHERE post.ipfs_cid = $1
@ -652,6 +697,7 @@ pub async fn get_post_by_ipfs_cid(
related_attachments=RELATED_ATTACHMENTS, related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS, related_mentions=RELATED_MENTIONS,
related_tags=RELATED_TAGS, related_tags=RELATED_TAGS,
related_links=RELATED_LINKS,
); );
let result = db_client.query_opt( let result = db_client.query_opt(
statement.as_str(), statement.as_str(),
@ -1086,21 +1132,44 @@ mod tests {
#[serial] #[serial]
async fn test_create_post() { async fn test_create_post() {
let db_client = &mut create_test_database().await; let db_client = &mut create_test_database().await;
let profile_data = ProfileCreateData { let author_data = ProfileCreateData {
username: "test".to_string(), username: "test".to_string(),
..Default::default() ..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 { let post_data = PostCreateData {
content: "test post".to_string(), content: "test post".to_string(),
..Default::default() ..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.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); 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] #[tokio::test]
#[serial] #[serial]
async fn test_update_post() { async fn test_update_post() {

View file

@ -94,6 +94,7 @@ pub struct Post {
pub attachments: Vec<DbMediaAttachment>, pub attachments: Vec<DbMediaAttachment>,
pub mentions: Vec<DbActorProfile>, pub mentions: Vec<DbActorProfile>,
pub tags: Vec<String>, pub tags: Vec<String>,
pub links: Vec<Uuid>,
pub object_id: Option<String>, pub object_id: Option<String>,
pub ipfs_cid: Option<String>, pub ipfs_cid: Option<String>,
pub token_id: Option<i32>, pub token_id: Option<i32>,
@ -115,6 +116,7 @@ impl Post {
db_attachments: Vec<DbMediaAttachment>, db_attachments: Vec<DbMediaAttachment>,
db_mentions: Vec<DbActorProfile>, db_mentions: Vec<DbActorProfile>,
db_tags: Vec<String>, db_tags: Vec<String>,
db_links: Vec<Uuid>,
) -> Result<Self, ConversionError> { ) -> Result<Self, ConversionError> {
// Consistency checks // Consistency checks
if db_post.author_id != db_author.id { if db_post.author_id != db_author.id {
@ -131,7 +133,8 @@ impl Post {
db_post.token_tx_id.is_some() || db_post.token_tx_id.is_some() ||
!db_attachments.is_empty() || !db_attachments.is_empty() ||
!db_mentions.is_empty() || !db_mentions.is_empty() ||
!db_tags.is_empty() !db_tags.is_empty() ||
!db_links.is_empty()
) { ) {
return Err(ConversionError); return Err(ConversionError);
}; };
@ -148,6 +151,7 @@ impl Post {
attachments: db_attachments, attachments: db_attachments,
mentions: db_mentions, mentions: db_mentions,
tags: db_tags, tags: db_tags,
links: db_links,
object_id: db_post.object_id, object_id: db_post.object_id,
ipfs_cid: db_post.ipfs_cid, ipfs_cid: db_post.ipfs_cid,
token_id: db_post.token_id, token_id: db_post.token_id,
@ -189,6 +193,7 @@ impl Default for Post {
attachments: vec![], attachments: vec![],
mentions: vec![], mentions: vec![],
tags: vec![], tags: vec![],
links: vec![],
object_id: None, object_id: None,
ipfs_cid: None, ipfs_cid: None,
token_id: None, token_id: None,
@ -212,7 +217,15 @@ impl TryFrom<&Row> for Post {
let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?; let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?;
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?; let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
let db_tags: Vec<String> = row.try_get("tags")?; let db_tags: Vec<String> = row.try_get("tags")?;
let post = Self::new(db_post, db_profile, db_attachments, db_mentions, db_tags)?; let db_links: Vec<Uuid> = row.try_get("links")?;
let post = Self::new(
db_post,
db_profile,
db_attachments,
db_mentions,
db_tags,
db_links,
)?;
Ok(post) Ok(post)
} }
} }
@ -226,6 +239,7 @@ pub struct PostCreateData {
pub attachments: Vec<Uuid>, pub attachments: Vec<Uuid>,
pub mentions: Vec<Uuid>, pub mentions: Vec<Uuid>,
pub tags: Vec<String>, pub tags: Vec<String>,
pub links: Vec<Uuid>,
pub object_id: Option<String>, pub object_id: Option<String>,
pub created_at: Option<DateTime<Utc>>, pub created_at: Option<DateTime<Utc>>,
} }
@ -267,6 +281,7 @@ mod tests {
attachments: vec![], attachments: vec![],
mentions: vec![], mentions: vec![],
tags: vec![], tags: vec![],
links: vec![],
object_id: None, object_id: None,
created_at: None, created_at: None,
}; };
@ -283,6 +298,7 @@ mod tests {
attachments: vec![], attachments: vec![],
mentions: vec![], mentions: vec![],
tags: vec![], tags: vec![],
links: vec![],
object_id: None, object_id: None,
created_at: None, created_at: None,
}; };