Handle Update(Note) activities
This commit is contained in:
parent
50699b5ab5
commit
dc34c980f6
11 changed files with 115 additions and 13 deletions
|
@ -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
|
||||
|
|
1
migrations/V0024__post__add_updated_at.sql
Normal file
1
migrations/V0024__post__add_updated_at.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE post ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE;
|
|
@ -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)
|
||||
);
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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"))?
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod create_note;
|
||||
pub mod update_note;
|
||||
pub mod update_person;
|
||||
|
|
37
src/activitypub/inbox/update_note.rs
Normal file
37
src/activitypub/inbox/update_note.rs
Normal 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(())
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in a new issue