Create post_link table to store links between posts
This commit is contained in:
parent
fcf7db97cb
commit
d9def75b32
8 changed files with 122 additions and 19 deletions
5
migrations/V0027__post_link.sql
Normal file
5
migrations/V0027__post_link.sql
Normal 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)
|
||||
);
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -163,6 +163,7 @@ impl TryFrom<StatusData> 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,
|
||||
};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -97,12 +97,14 @@ impl TryFrom<&Row> for Notification {
|
|||
let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?;
|
||||
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
|
||||
let db_tags: Vec<String> = row.try_get("tags")?;
|
||||
let db_links: Vec<Uuid> = row.try_get("links")?;
|
||||
let post = Post::new(
|
||||
db_post,
|
||||
db_post_author,
|
||||
db_attachments,
|
||||
db_mentions,
|
||||
db_tags,
|
||||
db_links,
|
||||
)?;
|
||||
Some(post)
|
||||
},
|
||||
|
|
|
@ -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<DbMediaAttachment> = attachments_rows.iter()
|
||||
.map(|row| row.try_get("media_attachment"))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
@ -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<String> = tags_rows.iter()
|
||||
.map(|row| row.try_get("tag_name"))
|
||||
.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
|
||||
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() {
|
||||
|
|
|
@ -94,6 +94,7 @@ pub struct Post {
|
|||
pub attachments: Vec<DbMediaAttachment>,
|
||||
pub mentions: Vec<DbActorProfile>,
|
||||
pub tags: Vec<String>,
|
||||
pub links: Vec<Uuid>,
|
||||
pub object_id: Option<String>,
|
||||
pub ipfs_cid: Option<String>,
|
||||
pub token_id: Option<i32>,
|
||||
|
@ -115,6 +116,7 @@ impl Post {
|
|||
db_attachments: Vec<DbMediaAttachment>,
|
||||
db_mentions: Vec<DbActorProfile>,
|
||||
db_tags: Vec<String>,
|
||||
db_links: Vec<Uuid>,
|
||||
) -> Result<Self, ConversionError> {
|
||||
// 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<DbMediaAttachment> = row.try_get("attachments")?;
|
||||
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -226,6 +239,7 @@ pub struct PostCreateData {
|
|||
pub attachments: Vec<Uuid>,
|
||||
pub mentions: Vec<Uuid>,
|
||||
pub tags: Vec<String>,
|
||||
pub links: Vec<Uuid>,
|
||||
pub object_id: Option<String>,
|
||||
pub created_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue