Replace post attachments and other related objects when processing Update(Note) activity

This commit is contained in:
silverpill 2023-01-30 03:10:07 +00:00
parent 86beb532e2
commit bc19a524c4
5 changed files with 105 additions and 13 deletions

View file

@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- Replace post attachments and other related objects when processing `Update(Note)` activity.
### Changed ### Changed
- Use proof suites with prefix `Mitra`. - Use proof suites with prefix `Mitra`.

View file

@ -131,7 +131,7 @@ fn is_gnu_social_link(author_id: &str, attachment: &Attachment) -> bool {
} }
} }
async fn get_object_attachments( pub async fn get_object_attachments(
config: &Config, config: &Config,
db_client: &impl DatabaseClient, db_client: &impl DatabaseClient,
object: &Object, object: &Object,
@ -197,7 +197,7 @@ async fn get_object_attachments(
Ok(attachments) Ok(attachments)
} }
async fn get_object_tags( pub async fn get_object_tags(
config: &Config, config: &Config,
db_client: &impl DatabaseClient, db_client: &impl DatabaseClient,
object: &Object, object: &Object,

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use chrono::Utc; use chrono::Utc;
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
@ -7,7 +9,11 @@ use crate::activitypub::{
helpers::update_remote_profile, helpers::update_remote_profile,
types::Actor, types::Actor,
}, },
handlers::create::get_object_content, handlers::create::{
get_object_attachments,
get_object_content,
get_object_tags,
},
types::Object, types::Object,
vocabulary::{NOTE, PERSON}, vocabulary::{NOTE, PERSON},
}; };
@ -25,24 +31,48 @@ use crate::models::{
use super::HandlerResult; use super::HandlerResult;
async fn handle_update_note( async fn handle_update_note(
config: &Config,
db_client: &mut impl DatabaseClient, db_client: &mut impl DatabaseClient,
activity: Value, activity: Value,
) -> HandlerResult { ) -> HandlerResult {
let object: Object = serde_json::from_value(activity["object"].to_owned()) let object: Object = serde_json::from_value(activity["object"].to_owned())
.map_err(|_| ValidationError("invalid object"))?; .map_err(|_| ValidationError("invalid object"))?;
let post_id = match get_post_by_remote_object_id( let post = match get_post_by_remote_object_id(
db_client, db_client,
&object.id, &object.id,
).await { ).await {
Ok(post) => post.id, Ok(post) => post,
// Ignore Update if post is not found locally // Ignore Update if post is not found locally
Err(DatabaseError::NotFound(_)) => return Ok(None), Err(DatabaseError::NotFound(_)) => return Ok(None),
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}; };
let content = get_object_content(&object)?; let content = get_object_content(&object)?;
let updated_at = object.updated.unwrap_or(Utc::now()); let updated_at = object.updated.unwrap_or(Utc::now());
let post_data = PostUpdateData { content, updated_at }; let attachments = get_object_attachments(
update_post(db_client, &post_id, post_data).await?; config,
db_client,
&object,
&post.author,
).await?;
if content.is_empty() && attachments.is_empty() {
return Err(ValidationError("post is empty").into());
};
let (mentions, hashtags, links, emojis) = get_object_tags(
config,
db_client,
&object,
&HashMap::new(),
).await?;
let post_data = PostUpdateData {
content,
attachments,
mentions,
tags: hashtags,
links,
emojis,
updated_at,
};
update_post(db_client, &post.id, post_data).await?;
Ok(Some(NOTE)) Ok(Some(NOTE))
} }
@ -85,7 +115,7 @@ pub async fn handle_update(
.ok_or(ValidationError("unknown object type"))?; .ok_or(ValidationError("unknown object type"))?;
match object_type { match object_type {
NOTE => { NOTE => {
handle_update_note(db_client, activity).await handle_update_note(config, db_client, activity).await
}, },
PERSON => { PERSON => {
handle_update_person(config, db_client, activity).await handle_update_person(config, db_client, activity).await

View file

@ -306,12 +306,13 @@ pub async fn create_post(
} }
pub async fn update_post( pub async fn update_post(
db_client: &impl DatabaseClient, db_client: &mut impl DatabaseClient,
post_id: &Uuid, post_id: &Uuid,
post_data: PostUpdateData, post_data: PostUpdateData,
) -> Result<(), DatabaseError> { ) -> Result<(), DatabaseError> {
let transaction = db_client.transaction().await?;
// Reposts and immutable posts can't be updated // Reposts and immutable posts can't be updated
let updated_count = db_client.execute( let maybe_row = transaction.query_opt(
" "
UPDATE post UPDATE post
SET SET
@ -320,6 +321,7 @@ pub async fn update_post(
WHERE id = $3 WHERE id = $3
AND repost_of_id IS NULL AND repost_of_id IS NULL
AND ipfs_cid IS NULL AND ipfs_cid IS NULL
RETURNING post
", ",
&[ &[
&post_data.content, &post_data.content,
@ -327,9 +329,58 @@ pub async fn update_post(
&post_id, &post_id,
], ],
).await?; ).await?;
if updated_count == 0 { let row = maybe_row.ok_or(DatabaseError::NotFound("post"))?;
return Err(DatabaseError::NotFound("post")); let db_post: DbPost = row.try_get("post")?;
};
// Delete and re-create related objects
transaction.execute(
"DELETE FROM media_attachment WHERE post_id = $1",
&[&db_post.id],
).await?;
transaction.execute(
"DELETE FROM mention WHERE post_id = $1",
&[&db_post.id],
).await?;
transaction.execute(
"DELETE FROM post_tag WHERE post_id = $1",
&[&db_post.id],
).await?;
transaction.execute(
"DELETE FROM post_link WHERE source_id = $1",
&[&db_post.id],
).await?;
transaction.execute(
"DELETE FROM post_emoji WHERE post_id = $1",
&[&db_post.id],
).await?;
create_post_attachments(
&transaction,
&db_post.id,
&db_post.author_id,
post_data.attachments,
).await?;
create_post_mentions(
&transaction,
&db_post.id,
post_data.mentions,
).await?;
create_post_tags(
&transaction,
&db_post.id,
post_data.tags,
).await?;
create_post_links(
&transaction,
&db_post.id,
post_data.links,
).await?;
create_post_emojis(
&transaction,
&db_post.id,
post_data.emojis,
).await?;
transaction.commit().await?;
Ok(()) Ok(())
} }
@ -1323,6 +1374,7 @@ mod tests {
let post_data = PostUpdateData { let post_data = PostUpdateData {
content: "test update".to_string(), content: "test update".to_string(),
updated_at: Utc::now(), updated_at: Utc::now(),
..Default::default()
}; };
update_post(db_client, &post.id, post_data).await.unwrap(); update_post(db_client, &post.id, post_data).await.unwrap();
let post = get_post_by_id(db_client, &post.id).await.unwrap(); let post = get_post_by_id(db_client, &post.id).await.unwrap();

View file

@ -276,7 +276,13 @@ impl PostCreateData {
} }
} }
#[cfg_attr(test, derive(Default))]
pub struct PostUpdateData { pub struct PostUpdateData {
pub content: String, pub content: String,
pub attachments: Vec<Uuid>,
pub mentions: Vec<Uuid>,
pub tags: Vec<String>,
pub links: Vec<Uuid>,
pub emojis: Vec<Uuid>,
pub updated_at: DateTime<Utc>, pub updated_at: DateTime<Utc>,
} }