Store mentions in database

This commit is contained in:
silverpill 2021-11-11 19:10:28 +00:00
parent 06010e4403
commit 4da44159ed
9 changed files with 103 additions and 11 deletions

View file

@ -0,0 +1,5 @@
CREATE TABLE mention (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, profile_id)
);

View file

@ -75,6 +75,12 @@ CREATE TABLE media_attachment (
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);
CREATE TABLE mention (
post_id UUID NOT NULL REFERENCES post (id) ON DELETE CASCADE,
profile_id UUID NOT NULL REFERENCES actor_profile (id) ON DELETE CASCADE,
PRIMARY KEY (post_id, profile_id)
);
CREATE TABLE oauth_token (
id SERIAL PRIMARY KEY,
owner_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,

View file

@ -196,6 +196,7 @@ pub async fn process_note(
content,
in_reply_to_id,
attachments: attachments,
mentions: vec![],
object_id: Some(object.id),
created_at: object.published,
};

View file

@ -5,6 +5,27 @@ use uuid::Uuid;
use crate::mastodon_api::accounts::types::Account;
use crate::mastodon_api::media::types::Attachment;
use crate::models::posts::types::{Post, PostCreateData};
use crate::models::profiles::types::DbActorProfile;
/// https://docs.joinmastodon.org/entities/mention/
#[derive(Serialize)]
pub struct Mention {
id: String,
username: String,
acct: String,
url: String,
}
impl Mention {
fn from_profile(profile: DbActorProfile, instance_url: &str) -> Self {
Mention {
id: profile.id.to_string(),
username: profile.username.clone(),
acct: profile.acct.clone(),
url: profile.actor_id(instance_url).unwrap(),
}
}
}
/// https://docs.joinmastodon.org/entities/status/
#[derive(Serialize)]
@ -17,6 +38,7 @@ pub struct Status {
pub replies_count: i32,
pub favourites_count: i32,
pub media_attachments: Vec<Attachment>,
mentions: Vec<Mention>,
// Authorized user attributes
pub favourited: bool,
@ -32,6 +54,9 @@ impl Status {
let attachments: Vec<Attachment> = post.attachments.into_iter()
.map(|item| Attachment::from_db(item, instance_url))
.collect();
let mentions: Vec<Mention> = post.mentions.into_iter()
.map(|item| Mention::from_profile(item, instance_url))
.collect();
let account = Account::from_profile(post.author, instance_url);
Self {
id: post.id,
@ -42,6 +67,7 @@ impl Status {
replies_count: post.reply_count,
favourites_count: post.reaction_count,
media_attachments: attachments,
mentions: mentions,
favourited: post.actions.map_or(false, |actions| actions.favourited),
ipfs_cid: post.ipfs_cid,
token_id: post.token_id,
@ -68,6 +94,7 @@ impl From<StatusData> for PostCreateData {
content: value.status,
in_reply_to_id: value.in_reply_to_id,
attachments: value.media_ids.unwrap_or(vec![]),
mentions: vec![],
object_id: None,
created_at: None,
}

View file

@ -62,6 +62,8 @@ async fn create_status(
&instance.url(),
&post_data.content,
);
post_data.mentions = mention_map.values()
.map(|profile| profile.id).collect();
let post = create_post(db_client, &current_user.id, post_data).await?;
// Federate
let maybe_in_reply_to = match post.in_reply_to_id {

View file

@ -5,7 +5,7 @@ use uuid::Uuid;
use crate::errors::DatabaseError;
use crate::models::posts::helpers::get_actions_for_posts;
use crate::models::posts::queries::RELATED_ATTACHMENTS;
use crate::models::posts::queries::{RELATED_ATTACHMENTS, RELATED_MENTIONS};
use super::types::{EventType, Notification};
async fn create_notification(
@ -73,7 +73,8 @@ pub async fn get_notifications(
"
SELECT
notification, sender, post, post_author,
{related_attachments}
{related_attachments},
{related_mentions}
FROM notification
JOIN actor_profile AS sender
ON notification.sender_id = sender.id
@ -85,6 +86,7 @@ pub async fn get_notifications(
ORDER BY notification.created_at DESC
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let rows = db_client.query(
statement.as_str(),

View file

@ -75,7 +75,8 @@ impl TryFrom<&Row> for Notification {
Some(db_post) => {
let db_post_author: DbActorProfile = row.try_get("post_author")?;
let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?;
Some(Post::new(db_post, db_post_author, db_attachments))
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
Some(Post::new(db_post, db_post_author, db_attachments, db_mentions))
},
None => None,
};

View file

@ -13,6 +13,7 @@ use crate::models::cleanup::{
};
use crate::models::notifications::queries::create_reply_notification;
use crate::models::profiles::queries::update_post_count;
use crate::models::profiles::types::DbActorProfile;
use super::types::{DbPost, Post, PostCreateData};
pub const RELATED_ATTACHMENTS: &str =
@ -21,6 +22,14 @@ pub const RELATED_ATTACHMENTS: &str =
FROM media_attachment WHERE post_id = post.id
) AS attachments";
pub const RELATED_MENTIONS: &str =
"ARRAY(
SELECT actor_profile
FROM mention
JOIN actor_profile ON mention.profile_id = actor_profile.id
WHERE post_id = post.id
) AS mentions";
pub async fn get_home_timeline(
db_client: &impl GenericClient,
current_user_id: &Uuid,
@ -31,7 +40,8 @@ pub async fn get_home_timeline(
"
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN actor_profile ON post.author_id = actor_profile.id
WHERE
@ -46,6 +56,7 @@ pub async fn get_home_timeline(
ORDER BY post.created_at DESC
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let rows = db_client.query(
statement.as_str(),
@ -71,13 +82,15 @@ pub async fn get_posts_by_author(
"
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN actor_profile ON post.author_id = actor_profile.id
WHERE {condition}
ORDER BY post.created_at DESC
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
condition=condition,
);
let rows = db_client.query(
@ -136,6 +149,25 @@ pub async fn create_post(
let db_attachments: Vec<DbMediaAttachment> = attachments_rows.iter()
.map(|row| row.try_get("media_attachment"))
.collect::<Result<_, _>>()?;
// Create mentions
let mentions_rows = transaction.query(
"
INSERT INTO mention (post_id, profile_id)
SELECT $1, unnest($2::uuid[])
RETURNING (
SELECT actor_profile FROM actor_profile
WHERE actor_profile.id = profile_id
) AS actor_profile
",
&[&db_post.id, &data.mentions],
).await?;
if mentions_rows.len() != data.mentions.len() {
// Some profiles were not found
return Err(DatabaseError::NotFound("profile"));
};
let db_mentions: Vec<DbActorProfile> = mentions_rows.iter()
.map(|row| row.try_get("actor_profile"))
.collect::<Result<_, _>>()?;
// Update counters
let author = update_post_count(&transaction, &db_post.author_id, 1).await?;
if let Some(in_reply_to_id) = &db_post.in_reply_to_id {
@ -152,7 +184,7 @@ pub async fn create_post(
}
transaction.commit().await?;
let post = Post::new(db_post, author, db_attachments);
let post = Post::new(db_post, author, db_attachments, db_mentions);
Ok(post)
}
@ -164,12 +196,14 @@ pub async fn get_post_by_id(
"
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.id = $1
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let maybe_row = db_client.query_opt(
statement.as_str(),
@ -208,13 +242,15 @@ pub async fn get_thread(
)
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN context ON post.id = context.id
JOIN actor_profile ON post.author_id = actor_profile.id
ORDER BY context.path
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let rows = db_client.query(
statement.as_str(),
@ -237,12 +273,14 @@ pub async fn get_post_by_object_id(
"
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.object_id = $1
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let maybe_row = db_client.query_opt(
statement.as_str(),
@ -261,12 +299,14 @@ pub async fn get_post_by_ipfs_cid(
"
SELECT
post, actor_profile,
{related_attachments}
{related_attachments},
{related_mentions}
FROM post
JOIN actor_profile ON post.author_id = actor_profile.id
WHERE post.ipfs_cid = $1
",
related_attachments=RELATED_ATTACHMENTS,
related_mentions=RELATED_MENTIONS,
);
let result = db_client.query_opt(
statement.as_str(),

View file

@ -39,6 +39,7 @@ pub struct Post {
pub reply_count: i32,
pub reaction_count: i32,
pub attachments: Vec<DbMediaAttachment>,
pub mentions: Vec<DbActorProfile>,
pub object_id: Option<String>,
pub ipfs_cid: Option<String>,
pub token_id: Option<i32>,
@ -52,6 +53,7 @@ impl Post {
db_post: DbPost,
db_author: DbActorProfile,
db_attachments: Vec<DbMediaAttachment>,
db_mentions: Vec<DbActorProfile>,
) -> Self {
assert_eq!(db_post.author_id, db_author.id);
Self {
@ -62,6 +64,7 @@ impl Post {
reply_count: db_post.reply_count,
reaction_count: db_post.reaction_count,
attachments: db_attachments,
mentions: db_mentions,
object_id: db_post.object_id,
ipfs_cid: db_post.ipfs_cid,
token_id: db_post.token_id,
@ -83,6 +86,7 @@ impl Default for Post {
reply_count: 0,
reaction_count: 0,
attachments: vec![],
mentions: vec![],
object_id: None,
ipfs_cid: None,
token_id: None,
@ -101,7 +105,8 @@ impl TryFrom<&Row> for Post {
let db_post: DbPost = row.try_get("post")?;
let db_profile: DbActorProfile = row.try_get("actor_profile")?;
let db_attachments: Vec<DbMediaAttachment> = row.try_get("attachments")?;
let post = Self::new(db_post, db_profile, db_attachments);
let db_mentions: Vec<DbActorProfile> = row.try_get("mentions")?;
let post = Self::new(db_post, db_profile, db_attachments, db_mentions);
Ok(post)
}
}
@ -110,6 +115,7 @@ pub struct PostCreateData {
pub content: String,
pub in_reply_to_id: Option<Uuid>,
pub attachments: Vec<Uuid>,
pub mentions: Vec<Uuid>,
pub object_id: Option<String>,
pub created_at: Option<DateTime<Utc>>,
}
@ -137,6 +143,7 @@ mod tests {
content: " ".to_string(),
in_reply_to_id: None,
attachments: vec![],
mentions: vec![],
object_id: None,
created_at: None,
};
@ -149,6 +156,7 @@ mod tests {
content: "test ".to_string(),
in_reply_to_id: None,
attachments: vec![],
mentions: vec![],
object_id: None,
created_at: None,
};