mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 12:01:08 +00:00
federate article updates using diffs
This commit is contained in:
parent
5f7837d843
commit
4e458650b8
15 changed files with 171 additions and 94 deletions
37
src/api.rs
37
src/api.rs
|
@ -1,10 +1,9 @@
|
||||||
use crate::database::DatabaseHandle;
|
use crate::database::DatabaseHandle;
|
||||||
|
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::activities::create_or_update_article::{
|
use crate::federation::activities::create_article::CreateArticle;
|
||||||
CreateOrUpdateArticle, CreateOrUpdateType,
|
use crate::federation::activities::update_article::UpdateArticle;
|
||||||
};
|
|
||||||
use crate::federation::objects::article::DbArticle;
|
use crate::federation::objects::article::DbArticle;
|
||||||
|
use crate::federation::objects::edit::DbEdit;
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
|
@ -15,7 +14,6 @@ use axum::{Form, Json, Router};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use crate::federation::objects::edit::DbEdit;
|
|
||||||
|
|
||||||
pub fn api_routes() -> Router {
|
pub fn api_routes() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
@ -29,7 +27,7 @@ pub fn api_routes() -> Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct CreateArticle {
|
pub struct CreateArticleData {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
@ -38,7 +36,7 @@ pub struct CreateArticle {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn create_article(
|
async fn create_article(
|
||||||
data: Data<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
Form(create_article): Form<CreateArticle>,
|
Form(create_article): Form<CreateArticleData>,
|
||||||
) -> MyResult<Json<DbArticle>> {
|
) -> MyResult<Json<DbArticle>> {
|
||||||
let local_instance_id = data.local_instance().ap_id;
|
let local_instance_id = data.local_instance().ap_id;
|
||||||
let ap_id = Url::parse(&format!(
|
let ap_id = Url::parse(&format!(
|
||||||
|
@ -61,27 +59,21 @@ async fn create_article(
|
||||||
articles.insert(article.ap_id.inner().clone(), article.clone());
|
articles.insert(article.ap_id.inner().clone(), article.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateOrUpdateArticle::send_to_local_followers(
|
CreateArticle::send_to_followers(article.clone(), &data).await?;
|
||||||
article.clone(),
|
|
||||||
CreateOrUpdateType::Create,
|
|
||||||
&data,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(article))
|
Ok(Json(article))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct EditArticle {
|
pub struct EditArticleData {
|
||||||
pub ap_id: ObjectId<DbArticle>,
|
pub ap_id: ObjectId<DbArticle>,
|
||||||
pub new_text: String,
|
pub new_text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should create an edit object
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn edit_article(
|
async fn edit_article(
|
||||||
data: Data<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
Form(edit_article): Form<EditArticle>,
|
Form(edit_article): Form<EditArticleData>,
|
||||||
) -> MyResult<Json<DbArticle>> {
|
) -> MyResult<Json<DbArticle>> {
|
||||||
let original_article = {
|
let original_article = {
|
||||||
let mut lock = data.articles.lock().unwrap();
|
let mut lock = data.articles.lock().unwrap();
|
||||||
|
@ -93,28 +85,23 @@ async fn edit_article(
|
||||||
let mut lock = data.articles.lock().unwrap();
|
let mut lock = data.articles.lock().unwrap();
|
||||||
let article = lock.get_mut(edit_article.ap_id.inner()).unwrap();
|
let article = lock.get_mut(edit_article.ap_id.inner()).unwrap();
|
||||||
article.text = edit_article.new_text;
|
article.text = edit_article.new_text;
|
||||||
article.edits.push(edit);
|
article.edits.push(edit.clone());
|
||||||
article.clone()
|
article.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
CreateOrUpdateArticle::send_to_local_followers(
|
UpdateArticle::send_to_followers(updated_article.clone(), edit, &data).await?;
|
||||||
updated_article.clone(),
|
|
||||||
CreateOrUpdateType::Update,
|
|
||||||
&data,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(updated_article))
|
Ok(Json(updated_article))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct GetArticle {
|
pub struct GetArticleData {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn get_article(
|
async fn get_article(
|
||||||
Query(query): Query<GetArticle>,
|
Query(query): Query<GetArticleData>,
|
||||||
data: Data<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> MyResult<Json<DbArticle>> {
|
) -> MyResult<Json<DbArticle>> {
|
||||||
let articles = data.articles.lock().unwrap();
|
let articles = data.articles.lock().unwrap();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
use crate::utils::generate_object_id;
|
use crate::utils::generate_activity_id;
|
||||||
use crate::{database::DatabaseHandle, federation::activities::follow::Follow};
|
use crate::{database::DatabaseHandle, federation::activities::follow::Follow};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler,
|
config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler,
|
||||||
|
@ -20,7 +20,7 @@ pub struct Accept {
|
||||||
|
|
||||||
impl Accept {
|
impl Accept {
|
||||||
pub fn new(actor: ObjectId<DbInstance>, object: Follow) -> MyResult<Accept> {
|
pub fn new(actor: ObjectId<DbInstance>, object: Follow) -> MyResult<Accept> {
|
||||||
let id = generate_object_id(actor.inner())?;
|
let id = generate_activity_id(actor.inner())?;
|
||||||
Ok(Accept {
|
Ok(Accept {
|
||||||
actor,
|
actor,
|
||||||
object,
|
object,
|
||||||
|
|
|
@ -2,7 +2,8 @@ use crate::database::DatabaseHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::objects::article::{ApubArticle, DbArticle};
|
use crate::federation::objects::article::{ApubArticle, DbArticle};
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
use crate::utils::generate_object_id;
|
use crate::utils::generate_activity_id;
|
||||||
|
use activitypub_federation::kinds::activity::CreateType;
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
@ -12,57 +13,41 @@ use activitypub_federation::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
||||||
pub enum CreateOrUpdateType {
|
|
||||||
Create,
|
|
||||||
Update,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: temporary placeholder, later rework this to send diffs
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreateOrUpdateArticle {
|
pub struct CreateArticle {
|
||||||
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: ApubArticle,
|
pub object: ApubArticle,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub kind: CreateOrUpdateType,
|
pub kind: CreateType,
|
||||||
pub id: Url,
|
pub id: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateOrUpdateArticle {
|
impl CreateArticle {
|
||||||
pub async fn send_to_local_followers(
|
pub async fn send_to_followers(
|
||||||
article: DbArticle,
|
article: DbArticle,
|
||||||
kind: CreateOrUpdateType,
|
|
||||||
data: &Data<DatabaseHandle>,
|
data: &Data<DatabaseHandle>,
|
||||||
) -> MyResult<()> {
|
) -> MyResult<()> {
|
||||||
let local_instance = data.local_instance();
|
let local_instance = data.local_instance();
|
||||||
let to = local_instance
|
|
||||||
.followers
|
|
||||||
.iter()
|
|
||||||
.map(|f| f.ap_id.inner().clone())
|
|
||||||
.collect();
|
|
||||||
let object = article.clone().into_json(data).await?;
|
let object = article.clone().into_json(data).await?;
|
||||||
let id = generate_object_id(local_instance.ap_id.inner())?;
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
let create_or_update = CreateOrUpdateArticle {
|
let create_or_update = CreateArticle {
|
||||||
actor: local_instance.ap_id.clone(),
|
actor: local_instance.ap_id.clone(),
|
||||||
to,
|
to: local_instance.follower_ids(),
|
||||||
object,
|
object,
|
||||||
kind,
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
let inboxes = local_instance
|
local_instance
|
||||||
.followers
|
.send_to_followers(create_or_update, data)
|
||||||
.iter()
|
.await?;
|
||||||
.map(|f| f.inbox.clone())
|
|
||||||
.collect();
|
|
||||||
local_instance.send(create_or_update, inboxes, data).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ActivityHandler for CreateOrUpdateArticle {
|
impl ActivityHandler for CreateArticle {
|
||||||
type DataType = DatabaseHandle;
|
type DataType = DatabaseHandle;
|
||||||
type Error = crate::error::Error;
|
type Error = crate::error::Error;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::DbInstance;
|
||||||
use crate::{database::DatabaseHandle, federation::activities::accept::Accept, generate_object_id};
|
use crate::{
|
||||||
|
database::DatabaseHandle, federation::activities::accept::Accept, generate_activity_id,
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
@ -22,7 +24,7 @@ pub struct Follow {
|
||||||
|
|
||||||
impl Follow {
|
impl Follow {
|
||||||
pub fn new(actor: ObjectId<DbInstance>, object: ObjectId<DbInstance>) -> MyResult<Follow> {
|
pub fn new(actor: ObjectId<DbInstance>, object: ObjectId<DbInstance>) -> MyResult<Follow> {
|
||||||
let id = generate_object_id(actor.inner())?;
|
let id = generate_activity_id(actor.inner())?;
|
||||||
Ok(Follow {
|
Ok(Follow {
|
||||||
actor,
|
actor,
|
||||||
object,
|
object,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod accept;
|
pub mod accept;
|
||||||
pub mod create_or_update_article;
|
pub mod create_article;
|
||||||
pub mod follow;
|
pub mod follow;
|
||||||
|
pub mod update_article;
|
||||||
|
|
80
src/federation/activities/update_article.rs
Normal file
80
src/federation/activities/update_article.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
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::{
|
||||||
|
config::Data,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
protocol::helpers::deserialize_one_or_many,
|
||||||
|
traits::{ActivityHandler, Object},
|
||||||
|
};
|
||||||
|
use diffy::{apply, Patch};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdateArticle {
|
||||||
|
pub actor: ObjectId<DbInstance>,
|
||||||
|
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||||
|
pub to: Vec<Url>,
|
||||||
|
pub object: ObjectId<DbArticle>,
|
||||||
|
pub result: ApubEdit,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: CreateType,
|
||||||
|
pub id: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateArticle {
|
||||||
|
pub async fn send_to_followers(
|
||||||
|
article: DbArticle,
|
||||||
|
edit: DbEdit,
|
||||||
|
data: &Data<DatabaseHandle>,
|
||||||
|
) -> MyResult<()> {
|
||||||
|
let local_instance = data.local_instance();
|
||||||
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
|
let create_or_update = UpdateArticle {
|
||||||
|
actor: local_instance.ap_id.clone(),
|
||||||
|
to: local_instance.follower_ids(),
|
||||||
|
object: article.ap_id,
|
||||||
|
result: edit.into_json(data).await?,
|
||||||
|
kind: Default::default(),
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
local_instance
|
||||||
|
.send_to_followers(create_or_update, data)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ActivityHandler for UpdateArticle {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
let edit = DbEdit::from_json(self.result.clone(), data).await?;
|
||||||
|
let mut lock = data.articles.lock().unwrap();
|
||||||
|
let article = lock.get_mut(self.object.inner()).unwrap();
|
||||||
|
article.edits.push(edit);
|
||||||
|
// TODO: probably better to apply patch inside DbEdit::from_json()
|
||||||
|
let patch = Patch::from_str(&self.result.diff)?;
|
||||||
|
article.text = apply(&article.text, &patch)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
#[test]
|
|
||||||
fn test_diff() {
|
|
||||||
use diffy::create_patch;
|
|
||||||
|
|
||||||
let original = "The Way of Kings\nWords of Radiance\n";
|
|
||||||
let modified = "The Way of Kings\nWords of Radiance\nOathbringer\n";
|
|
||||||
|
|
||||||
let patch = create_patch(original, modified);
|
|
||||||
assert_eq!("--- original\n+++ modified\n@@ -1,2 +1,3 @@\n The Way of Kings\n Words of Radiance\n+Oathbringer\n", patch.to_string());
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ use std::sync::{Arc, Mutex};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub mod activities;
|
pub mod activities;
|
||||||
mod diff;
|
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
use activitypub_federation::traits::Object;
|
use activitypub_federation::traits::Object;
|
||||||
use diffy::create_patch;
|
use diffy::create_patch;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sha2::Digest;
|
||||||
use sha2::Sha224;
|
use sha2::Sha224;
|
||||||
use sha2::{Digest};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// Represents a single change to the article.
|
/// Represents a single change to the article.
|
||||||
|
@ -28,7 +28,7 @@ impl DbEdit {
|
||||||
Ok(DbEdit {
|
Ok(DbEdit {
|
||||||
id: edit_id,
|
id: edit_id,
|
||||||
diff: diff.to_string(),
|
diff: diff.to_string(),
|
||||||
local: true
|
local: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ pub struct ApubEdit {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: EditType,
|
kind: EditType,
|
||||||
id: ObjectId<DbEdit>,
|
id: ObjectId<DbEdit>,
|
||||||
diff: String,
|
pub(crate) diff: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
|
@ -75,7 +75,11 @@ impl Collection for DbEditCollection {
|
||||||
try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?;
|
try_join_all(apub.items.into_iter().map(|i| DbEdit::from_json(i, data))).await?;
|
||||||
let mut articles = data.articles.lock().unwrap();
|
let mut articles = data.articles.lock().unwrap();
|
||||||
let article = articles.get_mut(owner.ap_id.inner()).unwrap();
|
let article = articles.get_mut(owner.ap_id.inner()).unwrap();
|
||||||
let edit_ids = article.edits.iter().map(|e| e.id.clone()).collect::<Vec<_>>();
|
let edit_ids = article
|
||||||
|
.edits
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.id.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
for e in edits.clone() {
|
for e in edits.clone() {
|
||||||
if !edit_ids.contains(&&e.id) {
|
if !edit_ids.contains(&&e.id) {
|
||||||
article.edits.push(e);
|
article.edits.push(e);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::error::Error;
|
use crate::error::{Error, MyResult};
|
||||||
use crate::federation::objects::articles_collection::DbArticleCollection;
|
use crate::federation::objects::articles_collection::DbArticleCollection;
|
||||||
use crate::{database::DatabaseHandle, federation::activities::follow::Follow};
|
use crate::{database::DatabaseHandle, federation::activities::follow::Follow};
|
||||||
use activitypub_federation::activity_sending::SendActivityTask;
|
use activitypub_federation::activity_sending::SendActivityTask;
|
||||||
|
@ -40,10 +40,17 @@ pub struct Instance {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DbInstance {
|
impl DbInstance {
|
||||||
pub fn followers_url(&self) -> Result<Url, Error> {
|
pub fn followers_url(&self) -> MyResult<Url> {
|
||||||
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
|
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn follower_ids(&self) -> Vec<Url> {
|
||||||
|
self.followers
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.ap_id.inner().clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn follow(
|
pub async fn follow(
|
||||||
&self,
|
&self,
|
||||||
other: &DbInstance,
|
other: &DbInstance,
|
||||||
|
@ -55,7 +62,26 @@ impl DbInstance {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn send<Activity>(
|
pub async fn send_to_followers<Activity>(
|
||||||
|
&self,
|
||||||
|
activity: Activity,
|
||||||
|
data: &Data<DatabaseHandle>,
|
||||||
|
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||||
|
where
|
||||||
|
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
|
||||||
|
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
|
||||||
|
{
|
||||||
|
let local_instance = data.local_instance();
|
||||||
|
let inboxes = local_instance
|
||||||
|
.followers
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.inbox.clone())
|
||||||
|
.collect();
|
||||||
|
local_instance.send(activity, inboxes, data).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send<Activity>(
|
||||||
&self,
|
&self,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
recipients: Vec<Url>,
|
recipients: Vec<Url>,
|
||||||
|
|
|
@ -12,7 +12,8 @@ use activitypub_federation::traits::Object;
|
||||||
use activitypub_federation::traits::{ActivityHandler, Collection};
|
use activitypub_federation::traits::{ActivityHandler, Collection};
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
|
|
||||||
use crate::federation::activities::create_or_update_article::CreateOrUpdateArticle;
|
use crate::federation::activities::create_article::CreateArticle;
|
||||||
|
use crate::federation::activities::update_article::UpdateArticle;
|
||||||
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};
|
||||||
|
@ -82,7 +83,8 @@ async fn http_get_article_edits(
|
||||||
pub enum InboxActivities {
|
pub enum InboxActivities {
|
||||||
Follow(Follow),
|
Follow(Follow),
|
||||||
Accept(Accept),
|
Accept(Accept),
|
||||||
CreateOrUpdateArticle(CreateOrUpdateArticle),
|
CreateArticle(CreateArticle),
|
||||||
|
UpdateArticle(UpdateArticle),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::utils::generate_object_id;
|
use crate::utils::generate_activity_id;
|
||||||
|
|
||||||
use activitypub_federation::config::FederationMiddleware;
|
use activitypub_federation::config::FederationMiddleware;
|
||||||
use axum::{Router, Server};
|
use axum::{Router, Server};
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
/// Just generate random url as object id. In a real project, you probably want to use
|
pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> {
|
||||||
/// an url which contains the database id for easy retrieval (or store the random id in db).
|
|
||||||
pub fn generate_object_id(domain: &Url) -> Result<Url, ParseError> {
|
|
||||||
let port = domain.port().unwrap();
|
let port = domain.port().unwrap();
|
||||||
let domain = domain.domain().unwrap();
|
let domain = domain.domain().unwrap();
|
||||||
let id: String = thread_rng()
|
let id: String = thread_rng()
|
||||||
|
@ -11,5 +9,5 @@ pub fn generate_object_id(domain: &Url) -> Result<Url, ParseError> {
|
||||||
.take(7)
|
.take(7)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
Url::parse(&format!("http://{}:{}/objects/{}", domain,port, id))
|
Url::parse(&format!("http://{}:{}/objects/{}", domain, port, id))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ mod common;
|
||||||
|
|
||||||
use crate::common::{follow_instance, get_query, patch, post, TestData};
|
use crate::common::{follow_instance, get_query, patch, post, TestData};
|
||||||
use common::get;
|
use common::get;
|
||||||
use fediwiki::api::{CreateArticle, EditArticle, GetArticle, ResolveObject};
|
use fediwiki::api::{CreateArticleData, EditArticleData, GetArticleData, ResolveObject};
|
||||||
use fediwiki::error::MyResult;
|
use fediwiki::error::MyResult;
|
||||||
use fediwiki::federation::objects::article::DbArticle;
|
use fediwiki::federation::objects::article::DbArticle;
|
||||||
use fediwiki::federation::objects::instance::DbInstance;
|
use fediwiki::federation::objects::instance::DbInstance;
|
||||||
|
@ -17,7 +17,7 @@ async fn test_create_and_read_article() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start();
|
||||||
|
|
||||||
// error on nonexistent article
|
// error on nonexistent article
|
||||||
let get_article = GetArticle {
|
let get_article = GetArticleData {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
};
|
};
|
||||||
let not_found = get_query::<DbArticle, _>(
|
let not_found = get_query::<DbArticle, _>(
|
||||||
|
@ -29,7 +29,7 @@ async fn test_create_and_read_article() -> MyResult<()> {
|
||||||
assert!(not_found.is_err());
|
assert!(not_found.is_err());
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let create_article = CreateArticle {
|
let create_article = CreateArticleData {
|
||||||
title: get_article.title.to_string(),
|
title: get_article.title.to_string(),
|
||||||
text: "Lorem ipsum".to_string(),
|
text: "Lorem ipsum".to_string(),
|
||||||
};
|
};
|
||||||
|
@ -80,7 +80,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
let data = TestData::start();
|
let data = TestData::start();
|
||||||
|
|
||||||
// create article on alpha
|
// create article on alpha
|
||||||
let create_article = CreateArticle {
|
let create_article = CreateArticleData {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: "Lorem ipsum".to_string(),
|
text: "Lorem ipsum".to_string(),
|
||||||
};
|
};
|
||||||
|
@ -89,7 +89,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
assert!(create_res.local);
|
assert!(create_res.local);
|
||||||
|
|
||||||
// article is not yet on beta
|
// article is not yet on beta
|
||||||
let get_article = GetArticle {
|
let get_article = GetArticleData {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
};
|
};
|
||||||
let get_res = get_query::<DbArticle, _>(
|
let get_res = get_query::<DbArticle, _>(
|
||||||
|
@ -129,7 +129,7 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
||||||
follow_instance(data.hostname_alpha, data.hostname_beta).await?;
|
follow_instance(data.hostname_alpha, data.hostname_beta).await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticle {
|
let create_form = CreateArticleData {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: "Lorem ipsum".to_string(),
|
text: "Lorem ipsum".to_string(),
|
||||||
};
|
};
|
||||||
|
@ -137,7 +137,7 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
||||||
assert_eq!(create_res.title, create_form.title);
|
assert_eq!(create_res.title, create_form.title);
|
||||||
|
|
||||||
// article should be federated to alpha
|
// article should be federated to alpha
|
||||||
let get_article = GetArticle {
|
let get_article = GetArticleData {
|
||||||
title: create_res.title.clone(),
|
title: create_res.title.clone(),
|
||||||
};
|
};
|
||||||
let get_res =
|
let get_res =
|
||||||
|
@ -147,17 +147,20 @@ async fn test_federate_article_changes() -> MyResult<()> {
|
||||||
assert_eq!(create_res.text, get_res.text);
|
assert_eq!(create_res.text, get_res.text);
|
||||||
|
|
||||||
// edit the article
|
// edit the article
|
||||||
let edit_form = EditArticle {
|
let edit_form = EditArticleData {
|
||||||
ap_id: create_res.ap_id,
|
ap_id: create_res.ap_id,
|
||||||
new_text: "Lorem Ipsum 2".to_string(),
|
new_text: "Lorem Ipsum 2".to_string(),
|
||||||
};
|
};
|
||||||
let edit_res: DbArticle = patch(data.hostname_beta, "article", &edit_form).await?;
|
let edit_res: DbArticle = patch(data.hostname_beta, "article", &edit_form).await?;
|
||||||
assert_eq!(edit_res.text, edit_form.new_text);
|
assert_eq!(edit_res.text, edit_form.new_text);
|
||||||
assert_eq!(edit_res.edits.len(), 1);
|
assert_eq!(edit_res.edits.len(), 1);
|
||||||
assert!(edit_res.edits[0].id.to_string().starts_with(&edit_res.ap_id.to_string()));
|
assert!(edit_res.edits[0]
|
||||||
|
.id
|
||||||
|
.to_string()
|
||||||
|
.starts_with(&edit_res.ap_id.to_string()));
|
||||||
|
|
||||||
// edit should be federated to alpha
|
// edit should be federated to alpha
|
||||||
let get_article = GetArticle {
|
let get_article = GetArticleData {
|
||||||
title: edit_res.title.clone(),
|
title: edit_res.title.clone(),
|
||||||
};
|
};
|
||||||
let get_res =
|
let get_res =
|
||||||
|
|
Loading…
Reference in a new issue