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: id:
type: string type: string
format: uuid 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: content:
description: HTML-encoded post content. description: HTML-encoded post content.
type: string 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_id INTEGER,
token_tx_id VARCHAR(200), token_tx_id VARCHAR(200),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
UNIQUE (author_id, repost_of_id) UNIQUE (author_id, repost_of_id)
); );

View file

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

View file

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

View file

@ -1,2 +1,3 @@
pub mod create_note; pub mod create_note;
pub mod update_note;
pub mod update_person; 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, get_or_import_profile_by_actor_id,
import_post, import_post,
}; };
use super::inbox::update_note::handle_update_note;
use super::inbox::update_person::handle_update_person; use super::inbox::update_person::handle_update_person;
use super::vocabulary::*; use super::vocabulary::*;
@ -392,6 +393,13 @@ pub async fn receive_activity(
Err(other_error) => return Err(other_error.into()), 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) => { (UPDATE, PERSON) => {
require_actor_signature(&activity.actor, &signer_id)?; require_actor_signature(&activity.actor, &signer_id)?;
handle_update_person( handle_update_person(

View file

@ -53,6 +53,8 @@ pub struct Status {
pub id: Uuid, pub id: Uuid,
pub uri: String, pub uri: String,
pub created_at: DateTime<Utc>, 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 account: Account,
pub content: String, pub content: String,
pub in_reply_to_id: Option<Uuid>, pub in_reply_to_id: Option<Uuid>,
@ -104,6 +106,7 @@ impl Status {
id: post.id, id: post.id,
uri: object_id, uri: object_id,
created_at: post.created_at, created_at: post.created_at,
edited_at: post.updated_at,
account: account, account: account,
content: post.content, content: post.content,
in_reply_to_id: post.in_reply_to_id, 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::profiles::types::DbActorProfile;
use crate::models::relationships::types::RelationshipType; use crate::models::relationships::types::RelationshipType;
use crate::utils::id::new_uuid; 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( pub async fn create_post(
db_client: &mut impl GenericClient, db_client: &mut impl GenericClient,
@ -660,25 +666,24 @@ pub async fn get_post_by_ipfs_cid(
pub async fn update_post( pub async fn update_post(
db_client: &impl GenericClient, db_client: &impl GenericClient,
post: &Post, post_id: &Uuid,
post_data: PostUpdateData,
) -> Result<(), DatabaseError> { ) -> Result<(), DatabaseError> {
// Reposts can't be updated // Reposts and immutable posts can't be updated
let updated_count = db_client.execute( let updated_count = db_client.execute(
" "
UPDATE post UPDATE post
SET SET
content = $1, content = $1,
ipfs_cid = $2, updated_at = $2
token_id = $3, WHERE id = $3
token_tx_id = $4 AND repost_of_id IS NULL
WHERE id = $5 AND repost_of_id IS NULL AND ipfs_cid IS NULL
", ",
&[ &[
&post.content, &post_data.content,
&post.ipfs_cid, &post_data.updated_at,
&post.token_id, &post_id,
&post.token_tx_id,
&post.id,
], ],
).await?; ).await?;
if updated_count == 0 { if updated_count == 0 {
@ -1072,6 +1077,31 @@ mod tests {
let post = create_post(db_client, &profile.id, post_data).await.unwrap(); let post = create_post(db_client, &profile.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, 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] #[tokio::test]

View file

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