wip: separate activities for editing local/remote article

This commit is contained in:
Felix Ableitner 2023-11-27 11:47:33 +01:00
parent 8a11cfed20
commit c22df74b99
7 changed files with 117 additions and 61 deletions

View File

@ -1,7 +1,7 @@
use crate::database::DatabaseHandle;
use crate::error::MyResult;
use crate::federation::activities::create_article::CreateArticle;
use crate::federation::activities::update_article::UpdateArticle;
use crate::federation::activities::update_remote_article::UpdateRemoteArticle;
use crate::federation::objects::article::DbArticle;
use crate::federation::objects::edit::{ApubEdit, DbEdit, EditVersion};
use crate::federation::objects::instance::DbInstance;
@ -10,6 +10,7 @@ use activitypub_federation::fetch::object_id::ObjectId;
use anyhow::anyhow;
use crate::federation::activities::update_local_article::UpdateLocalArticle;
use axum::extract::Query;
use axum::routing::{get, post};
use axum::{Form, Json, Router};
@ -93,9 +94,9 @@ async fn edit_article(
article.clone()
};
UpdateArticle::send_to_followers(edit, updated_article.clone(), &data).await?;
UpdateLocalArticle::send(updated_article, &data).await?;
} else {
UpdateArticle::send_to_origin(
UpdateRemoteArticle::send(
edit,
original_article.instance.dereference(&data).await?,
&data,

View File

@ -2,4 +2,5 @@ pub mod accept;
pub mod create_article;
pub mod follow;
pub mod reject;
pub mod update_article;
pub mod update_local_article;
pub mod update_remote_article;

View File

@ -0,0 +1,71 @@
use crate::database::DatabaseHandle;
use crate::error::MyResult;
use crate::federation::objects::article::{ApubArticle, DbArticle};
use crate::federation::objects::instance::DbInstance;
use crate::utils::generate_activity_id;
use activitypub_federation::kinds::activity::UpdateType;
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::helpers::deserialize_one_or_many,
traits::{ActivityHandler, Object},
};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UpdateLocalArticle {
pub actor: ObjectId<DbInstance>,
#[serde(deserialize_with = "deserialize_one_or_many")]
pub to: Vec<Url>,
pub object: ApubArticle,
#[serde(rename = "type")]
pub kind: UpdateType,
pub id: Url,
}
impl UpdateLocalArticle {
/// Sent from article origin instance
pub async fn send(article: DbArticle, data: &Data<DatabaseHandle>) -> MyResult<()> {
debug_assert!(article.local);
let local_instance = data.local_instance();
let id = generate_activity_id(local_instance.ap_id.inner())?;
let update = UpdateLocalArticle {
actor: local_instance.ap_id.clone(),
to: local_instance.follower_ids(),
object: article.into_json(data).await?,
kind: Default::default(),
id,
};
local_instance.send_to_followers(update, data).await?;
Ok(())
}
}
#[async_trait::async_trait]
impl ActivityHandler for UpdateLocalArticle {
type DataType = DatabaseHandle;
type Error = crate::error::Error;
fn id(&self) -> &Url {
&self.id
}
fn actor(&self) -> &Url {
self.actor.inner()
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
/// Received on article follower instances (where article is always remote)
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
DbArticle::from_json(self.object, data).await?;
Ok(())
}
}

View File

@ -1,61 +1,45 @@
use crate::database::DatabaseHandle;
use crate::error::MyResult;
use crate::federation::objects::article::DbArticle;
use crate::federation::objects::edit::{ApubEdit, DbEdit};
use crate::federation::objects::instance::DbInstance;
use crate::utils::generate_activity_id;
use activitypub_federation::kinds::activity::CreateType;
use activitypub_federation::kinds::activity::UpdateType;
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::helpers::deserialize_one_or_many,
traits::{ActivityHandler, Object},
};
use diffy::{apply, Patch};
use crate::federation::activities::reject::RejectEdit;
use crate::federation::activities::update_local_article::UpdateLocalArticle;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UpdateArticle {
pub struct UpdateRemoteArticle {
pub actor: ObjectId<DbInstance>,
#[serde(deserialize_with = "deserialize_one_or_many")]
pub to: Vec<Url>,
pub object: ApubEdit,
#[serde(rename = "type")]
pub kind: CreateType,
pub kind: UpdateType,
pub id: Url,
}
impl UpdateArticle {
pub async fn send_to_followers(
edit: DbEdit,
article: DbArticle,
data: &Data<DatabaseHandle>,
) -> MyResult<()> {
debug_assert!(article.local);
let local_instance = data.local_instance();
let id = generate_activity_id(local_instance.ap_id.inner())?;
let update = UpdateArticle {
actor: local_instance.ap_id.clone(),
to: local_instance.follower_ids(),
object: edit.into_json(data).await?,
kind: Default::default(),
id,
};
local_instance.send_to_followers(update, data).await?;
Ok(())
}
pub async fn send_to_origin(
impl UpdateRemoteArticle {
/// Sent by a follower instance
pub async fn send(
edit: DbEdit,
article_instance: DbInstance,
data: &Data<DatabaseHandle>,
) -> MyResult<()> {
let local_instance = data.local_instance();
let id = generate_activity_id(local_instance.ap_id.inner())?;
let update = UpdateArticle {
let update = UpdateRemoteArticle {
actor: local_instance.ap_id.clone(),
to: vec![article_instance.ap_id.into_inner()],
object: edit.into_json(data).await?,
@ -68,8 +52,9 @@ impl UpdateArticle {
Ok(())
}
}
#[async_trait::async_trait]
impl ActivityHandler for UpdateArticle {
impl ActivityHandler for UpdateRemoteArticle {
type DataType = DatabaseHandle;
type Error = crate::error::Error;
@ -85,23 +70,30 @@ impl ActivityHandler for UpdateArticle {
Ok(())
}
/// Received on article origin instances
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
if DbEdit::from_json(self.object.clone(), data).await.is_ok() {
let article_local = {
let lock = data.articles.lock().unwrap();
let article = lock.get(self.object.object.inner()).unwrap();
article.local
};
match DbEdit::from_json(self.object.clone(), data).await {
Ok(edit) => {
let article = {
let lock = data.articles.lock().unwrap();
let article = lock.get(self.object.object.inner()).unwrap();
article.clone()
};
{
let patch = Patch::from_str(&edit.diff)?;
let applied = apply(&article.text, &patch)?;
let mut lock = data.articles.lock().unwrap();
let article = lock.get_mut(edit.article_id.inner()).unwrap();
article.edits.push(edit.clone());
article.text = applied;
}
if article_local {
// No need to wrap in announce, we can construct a new activity as all important info
// is in the object and result fields.
let local_instance = data.local_instance();
let id = generate_activity_id(local_instance.ap_id.inner())?;
let update = UpdateArticle {
let update = UpdateLocalArticle {
actor: local_instance.ap_id.clone(),
to: local_instance.follower_ids(),
object: self.object,
object: article.clone().into_json(data).await?,
kind: Default::default(),
id,
};
@ -109,9 +101,10 @@ impl ActivityHandler for UpdateArticle {
.send_to_followers(update, data)
.await?;
}
} else {
let user_instance = self.actor.dereference(data).await?;
RejectEdit::send(self.object.clone(), user_instance, data).await?;
Err(_e) => {
let user_instance = self.actor.dereference(data).await?;
RejectEdit::send(self.object.clone(), user_instance, data).await?;
}
}
Ok(())

View File

@ -42,7 +42,7 @@ pub struct ApubArticle {
pub(crate) attributed_to: ObjectId<DbInstance>,
#[serde(deserialize_with = "deserialize_one_or_many")]
pub(crate) to: Vec<Url>,
edits: CollectionId<DbEditCollection>,
pub edits: CollectionId<DbEditCollection>,
latest_version: EditVersion,
content: String,
name: String,

View File

@ -5,7 +5,7 @@ use crate::federation::objects::article::DbArticle;
use activitypub_federation::config::Data;
use activitypub_federation::fetch::object_id::ObjectId;
use activitypub_federation::traits::Object;
use diffy::{apply, create_patch, Patch};
use diffy::create_patch;
use serde::{Deserialize, Serialize};
use sha2::Digest;
use sha2::Sha224;
@ -104,21 +104,9 @@ impl Object for DbEdit {
version: json.version,
local: false,
};
let article_read = {
let lock = data.articles.lock().unwrap();
lock.get(edit.article_id.inner()).unwrap().clone()
};
let patch = Patch::from_str(&edit.diff)?;
// Dont apply the edit if we already fetched an update Article version.
// TODO: this assumes that we always receive edits in the correct order, probably need to
// include the parent for each edit
//if article_read.latest_version != edit.version {
let applied = apply(&article_read.text, &patch)?;
let mut lock = data.articles.lock().unwrap();
let article = lock.get_mut(edit.article_id.inner()).unwrap();
article.edits.push(edit.clone());
article.text = applied;
Ok(edit)
//}
}
}

View File

@ -14,7 +14,8 @@ use axum::extract::Path;
use crate::federation::activities::create_article::CreateArticle;
use crate::federation::activities::reject::RejectEdit;
use crate::federation::activities::update_article::UpdateArticle;
use crate::federation::activities::update_local_article::UpdateLocalArticle;
use crate::federation::activities::update_remote_article::UpdateRemoteArticle;
use crate::federation::objects::article::ApubArticle;
use crate::federation::objects::articles_collection::{ArticleCollection, DbArticleCollection};
use crate::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
@ -85,7 +86,8 @@ pub enum InboxActivities {
Follow(Follow),
Accept(Accept),
CreateArticle(CreateArticle),
UpdateArticle(UpdateArticle),
UpdateLocalArticle(UpdateLocalArticle),
UpdateRemoteArticle(UpdateRemoteArticle),
RejectEdit(RejectEdit),
}