mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 19:51:08 +00:00
wip: separate activities for editing local/remote article
This commit is contained in:
parent
8a11cfed20
commit
c22df74b99
7 changed files with 117 additions and 61 deletions
|
@ -1,7 +1,7 @@
|
||||||
use crate::database::DatabaseHandle;
|
use crate::database::DatabaseHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::activities::create_article::CreateArticle;
|
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::article::DbArticle;
|
||||||
use crate::federation::objects::edit::{ApubEdit, DbEdit, EditVersion};
|
use crate::federation::objects::edit::{ApubEdit, DbEdit, EditVersion};
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
|
@ -10,6 +10,7 @@ use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
|
use crate::federation::activities::update_local_article::UpdateLocalArticle;
|
||||||
use axum::extract::Query;
|
use axum::extract::Query;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::{Form, Json, Router};
|
use axum::{Form, Json, Router};
|
||||||
|
@ -93,9 +94,9 @@ async fn edit_article(
|
||||||
article.clone()
|
article.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
UpdateArticle::send_to_followers(edit, updated_article.clone(), &data).await?;
|
UpdateLocalArticle::send(updated_article, &data).await?;
|
||||||
} else {
|
} else {
|
||||||
UpdateArticle::send_to_origin(
|
UpdateRemoteArticle::send(
|
||||||
edit,
|
edit,
|
||||||
original_article.instance.dereference(&data).await?,
|
original_article.instance.dereference(&data).await?,
|
||||||
&data,
|
&data,
|
||||||
|
|
|
@ -2,4 +2,5 @@ pub mod accept;
|
||||||
pub mod create_article;
|
pub mod create_article;
|
||||||
pub mod follow;
|
pub mod follow;
|
||||||
pub mod reject;
|
pub mod reject;
|
||||||
pub mod update_article;
|
pub mod update_local_article;
|
||||||
|
pub mod update_remote_article;
|
||||||
|
|
71
src/federation/activities/update_local_article.rs
Normal file
71
src/federation/activities/update_local_article.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +1,45 @@
|
||||||
use crate::database::DatabaseHandle;
|
use crate::database::DatabaseHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::objects::article::DbArticle;
|
|
||||||
use crate::federation::objects::edit::{ApubEdit, DbEdit};
|
use crate::federation::objects::edit::{ApubEdit, DbEdit};
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
use crate::utils::generate_activity_id;
|
use crate::utils::generate_activity_id;
|
||||||
use activitypub_federation::kinds::activity::CreateType;
|
use activitypub_federation::kinds::activity::UpdateType;
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
};
|
};
|
||||||
|
use diffy::{apply, Patch};
|
||||||
|
|
||||||
use crate::federation::activities::reject::RejectEdit;
|
use crate::federation::activities::reject::RejectEdit;
|
||||||
|
use crate::federation::activities::update_local_article::UpdateLocalArticle;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UpdateArticle {
|
pub struct UpdateRemoteArticle {
|
||||||
pub actor: ObjectId<DbInstance>,
|
pub actor: ObjectId<DbInstance>,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub to: Vec<Url>,
|
pub to: Vec<Url>,
|
||||||
pub object: ApubEdit,
|
pub object: ApubEdit,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub kind: CreateType,
|
pub kind: UpdateType,
|
||||||
pub id: Url,
|
pub id: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateArticle {
|
impl UpdateRemoteArticle {
|
||||||
pub async fn send_to_followers(
|
/// Sent by a follower instance
|
||||||
edit: DbEdit,
|
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 = 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(
|
|
||||||
edit: DbEdit,
|
edit: DbEdit,
|
||||||
article_instance: DbInstance,
|
article_instance: DbInstance,
|
||||||
data: &Data<DatabaseHandle>,
|
data: &Data<DatabaseHandle>,
|
||||||
) -> MyResult<()> {
|
) -> MyResult<()> {
|
||||||
let local_instance = data.local_instance();
|
let local_instance = data.local_instance();
|
||||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
let update = UpdateArticle {
|
let update = UpdateRemoteArticle {
|
||||||
actor: local_instance.ap_id.clone(),
|
actor: local_instance.ap_id.clone(),
|
||||||
to: vec![article_instance.ap_id.into_inner()],
|
to: vec![article_instance.ap_id.into_inner()],
|
||||||
object: edit.into_json(data).await?,
|
object: edit.into_json(data).await?,
|
||||||
|
@ -68,8 +52,9 @@ impl UpdateArticle {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ActivityHandler for UpdateArticle {
|
impl ActivityHandler for UpdateRemoteArticle {
|
||||||
type DataType = DatabaseHandle;
|
type DataType = DatabaseHandle;
|
||||||
type Error = crate::error::Error;
|
type Error = crate::error::Error;
|
||||||
|
|
||||||
|
@ -85,23 +70,30 @@ impl ActivityHandler for UpdateArticle {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Received on article origin instances
|
||||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
if DbEdit::from_json(self.object.clone(), data).await.is_ok() {
|
match DbEdit::from_json(self.object.clone(), data).await {
|
||||||
let article_local = {
|
Ok(edit) => {
|
||||||
let lock = data.articles.lock().unwrap();
|
let article = {
|
||||||
let article = lock.get(self.object.object.inner()).unwrap();
|
let lock = data.articles.lock().unwrap();
|
||||||
article.local
|
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 local_instance = data.local_instance();
|
||||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
let update = UpdateArticle {
|
let update = UpdateLocalArticle {
|
||||||
actor: local_instance.ap_id.clone(),
|
actor: local_instance.ap_id.clone(),
|
||||||
to: local_instance.follower_ids(),
|
to: local_instance.follower_ids(),
|
||||||
object: self.object,
|
object: article.clone().into_json(data).await?,
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
|
@ -109,9 +101,10 @@ impl ActivityHandler for UpdateArticle {
|
||||||
.send_to_followers(update, data)
|
.send_to_followers(update, data)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
} else {
|
Err(_e) => {
|
||||||
let user_instance = self.actor.dereference(data).await?;
|
let user_instance = self.actor.dereference(data).await?;
|
||||||
RejectEdit::send(self.object.clone(), user_instance, data).await?;
|
RejectEdit::send(self.object.clone(), user_instance, data).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
|
@ -42,7 +42,7 @@ pub struct ApubArticle {
|
||||||
pub(crate) attributed_to: ObjectId<DbInstance>,
|
pub(crate) attributed_to: ObjectId<DbInstance>,
|
||||||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
pub(crate) to: Vec<Url>,
|
pub(crate) to: Vec<Url>,
|
||||||
edits: CollectionId<DbEditCollection>,
|
pub edits: CollectionId<DbEditCollection>,
|
||||||
latest_version: EditVersion,
|
latest_version: EditVersion,
|
||||||
content: String,
|
content: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::federation::objects::article::DbArticle;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
use activitypub_federation::traits::Object;
|
use activitypub_federation::traits::Object;
|
||||||
use diffy::{apply, create_patch, Patch};
|
use diffy::create_patch;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use sha2::Sha224;
|
use sha2::Sha224;
|
||||||
|
@ -104,21 +104,9 @@ impl Object for DbEdit {
|
||||||
version: json.version,
|
version: json.version,
|
||||||
local: false,
|
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 mut lock = data.articles.lock().unwrap();
|
||||||
let article = lock.get_mut(edit.article_id.inner()).unwrap();
|
let article = lock.get_mut(edit.article_id.inner()).unwrap();
|
||||||
article.edits.push(edit.clone());
|
article.edits.push(edit.clone());
|
||||||
article.text = applied;
|
|
||||||
Ok(edit)
|
Ok(edit)
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ use axum::extract::Path;
|
||||||
|
|
||||||
use crate::federation::activities::create_article::CreateArticle;
|
use crate::federation::activities::create_article::CreateArticle;
|
||||||
use crate::federation::activities::reject::RejectEdit;
|
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::article::ApubArticle;
|
||||||
use crate::federation::objects::articles_collection::{ArticleCollection, DbArticleCollection};
|
use crate::federation::objects::articles_collection::{ArticleCollection, DbArticleCollection};
|
||||||
use crate::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
|
use crate::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
|
||||||
|
@ -85,7 +86,8 @@ pub enum InboxActivities {
|
||||||
Follow(Follow),
|
Follow(Follow),
|
||||||
Accept(Accept),
|
Accept(Accept),
|
||||||
CreateArticle(CreateArticle),
|
CreateArticle(CreateArticle),
|
||||||
UpdateArticle(UpdateArticle),
|
UpdateLocalArticle(UpdateLocalArticle),
|
||||||
|
UpdateRemoteArticle(UpdateRemoteArticle),
|
||||||
RejectEdit(RejectEdit),
|
RejectEdit(RejectEdit),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue