Handle Update(Note) activities

This commit is contained in:
silverpill 2022-05-11 12:50:36 +00:00
parent 50699b5ab5
commit dc34c980f6
11 changed files with 115 additions and 13 deletions

View file

@ -864,6 +864,15 @@ components:
id:
type: string
format: uuid
created_at:
description: The date when this post was created.
type: string
format: dateTime
edited_at:
description: The date when this post was edited.
type: string
format: dateTime
nullable: true
content:
description: HTML-encoded post content.
type: string

View file

@ -0,0 +1 @@
ALTER TABLE post ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE;

View file

@ -46,6 +46,7 @@ CREATE TABLE post (
token_id INTEGER,
token_tx_id VARCHAR(200),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
UNIQUE (author_id, repost_of_id)
);

View file

@ -90,6 +90,9 @@ pub struct Object {
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<DateTime<Utc>>,
}
#[derive(Serialize)]

View file

@ -48,7 +48,7 @@ fn get_note_author_id(object: &Object) -> Result<String, ValidationError> {
const CONTENT_MAX_SIZE: usize = 100000;
fn get_note_content(object: &Object) -> Result<String, ValidationError> {
pub fn get_note_content(object: &Object) -> Result<String, ValidationError> {
let content = if object.object_type == PAGE {
// Lemmy Page
object.name.as_ref().ok_or(ValidationError("no content"))?

View file

@ -1,2 +1,3 @@
pub mod create_note;
pub mod update_note;
pub mod update_person;

View file

@ -0,0 +1,37 @@
use chrono::Utc;
use tokio_postgres::GenericClient;
use crate::activitypub::activity::Object;
use crate::activitypub::fetcher::helpers::ImportError;
use crate::activitypub::receiver::parse_object_id;
use crate::errors::DatabaseError;
use crate::models::posts::queries::{
get_post_by_object_id,
update_post,
};
use crate::models::posts::types::PostUpdateData;
use super::create_note::get_note_content;
pub async fn handle_update_note(
db_client: &mut impl GenericClient,
instance_url: &str,
object: Object,
) -> Result<(), ImportError> {
let post_id = match parse_object_id(instance_url, &object.id) {
Ok(post_id) => post_id,
Err(_) => {
let post = match get_post_by_object_id(db_client, &object.id).await {
Ok(post) => post,
// Ignore Update if post is not found locally
Err(DatabaseError::NotFound(_)) => return Ok(()),
Err(other_error) => return Err(other_error.into()),
};
post.id
},
};
let content = get_note_content(&object)?;
let updated_at = object.updated.unwrap_or(Utc::now());
let post_data = PostUpdateData { content, updated_at };
update_post(db_client, &post_id, post_data).await?;
Ok(())
}

View file

@ -41,6 +41,7 @@ use super::fetcher::helpers::{
get_or_import_profile_by_actor_id,
import_post,
};
use super::inbox::update_note::handle_update_note;
use super::inbox::update_person::handle_update_person;
use super::vocabulary::*;
@ -392,6 +393,13 @@ pub async fn receive_activity(
Err(other_error) => return Err(other_error.into()),
}
},
(UPDATE, NOTE) => {
require_actor_signature(&activity.actor, &signer_id)?;
let object: Object = serde_json::from_value(activity.object)
.map_err(|_| ValidationError("invalid object"))?;
handle_update_note(db_client, &config.instance_url(), object).await?;
NOTE
},
(UPDATE, PERSON) => {
require_actor_signature(&activity.actor, &signer_id)?;
handle_update_person(

View file

@ -53,6 +53,8 @@ pub struct Status {
pub id: Uuid,
pub uri: String,
pub created_at: DateTime<Utc>,
// Undocumented https://github.com/mastodon/mastodon/blob/v3.5.2/app/serializers/rest/status_serializer.rb
edited_at: Option<DateTime<Utc>>,
pub account: Account,
pub content: String,
pub in_reply_to_id: Option<Uuid>,
@ -104,6 +106,7 @@ impl Status {
id: post.id,
uri: object_id,
created_at: post.created_at,
edited_at: post.updated_at,
account: account,
content: post.content,
in_reply_to_id: post.in_reply_to_id,

View file

@ -22,7 +22,13 @@ use crate::models::profiles::queries::update_post_count;
use crate::models::profiles::types::DbActorProfile;
use crate::models::relationships::types::RelationshipType;
use crate::utils::id::new_uuid;
use super::types::{DbPost, Post, PostCreateData, Visibility};
use super::types::{
DbPost,
Post,
PostCreateData,
PostUpdateData,
Visibility,
};
pub async fn create_post(
db_client: &mut impl GenericClient,
@ -660,25 +666,24 @@ pub async fn get_post_by_ipfs_cid(
pub async fn update_post(
db_client: &impl GenericClient,
post: &Post,
post_id: &Uuid,
post_data: PostUpdateData,
) -> Result<(), DatabaseError> {
// Reposts can't be updated
// Reposts and immutable posts can't be updated
let updated_count = db_client.execute(
"
UPDATE post
SET
content = $1,
ipfs_cid = $2,
token_id = $3,
token_tx_id = $4
WHERE id = $5 AND repost_of_id IS NULL
updated_at = $2
WHERE id = $3
AND repost_of_id IS NULL
AND ipfs_cid IS NULL
",
&[
&post.content,
&post.ipfs_cid,
&post.token_id,
&post.token_tx_id,
&post.id,
&post_data.content,
&post_data.updated_at,
&post_id,
],
).await?;
if updated_count == 0 {
@ -1072,6 +1077,31 @@ mod tests {
let post = create_post(db_client, &profile.id, post_data).await.unwrap();
assert_eq!(post.content, "test post");
assert_eq!(post.author.id, profile.id);
assert_eq!(post.updated_at, None);
}
#[tokio::test]
#[serial]
async fn test_update_post() {
let db_client = &mut create_test_database().await;
let user_data = UserCreateData {
username: "test".to_string(),
..Default::default()
};
let user = create_user(db_client, user_data).await.unwrap();
let post_data = PostCreateData {
content: "test post".to_string(),
..Default::default()
};
let post = create_post(db_client, &user.id, post_data).await.unwrap();
let post_data = PostUpdateData {
content: "test update".to_string(),
updated_at: Utc::now(),
};
update_post(db_client, &post.id, post_data).await.unwrap();
let post = get_post_by_id(db_client, &post.id).await.unwrap();
assert_eq!(post.content, "test update");
assert_eq!(post.updated_at.is_some(), true);
}
#[tokio::test]

View file

@ -70,6 +70,7 @@ pub struct DbPost {
pub token_id: Option<i32>,
pub token_tx_id: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
}
// List of user's actions
@ -98,6 +99,7 @@ pub struct Post {
pub token_id: Option<i32>,
pub token_tx_id: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
// These fields are not populated automatically
// by functions in posts::queries module
@ -139,6 +141,7 @@ impl Post {
token_id: db_post.token_id,
token_tx_id: db_post.token_tx_id,
created_at: db_post.created_at,
updated_at: db_post.updated_at,
actions: None,
in_reply_to: None,
repost_of: None,
@ -179,6 +182,7 @@ impl Default for Post {
token_id: None,
token_tx_id: None,
created_at: Utc::now(),
updated_at: None,
actions: None,
in_reply_to: None,
repost_of: None,
@ -230,6 +234,11 @@ impl PostCreateData {
}
}
pub struct PostUpdateData {
pub content: String,
pub updated_at: DateTime<Utc>,
}
#[cfg(test)]
mod tests {
use super::*;