generate edit on article update (without federation)

This commit is contained in:
Felix Ableitner 2023-11-21 16:03:25 +01:00
parent afbb81c0d1
commit 5f7837d843
6 changed files with 50 additions and 11 deletions

1
Cargo.lock generated
View File

@ -625,6 +625,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serial_test", "serial_test",
"sha2",
"tokio", "tokio",
"tracing", "tracing",
"url", "url",

View File

@ -17,6 +17,7 @@ futures = "0.3.29"
rand = "0.8.5" rand = "0.8.5"
serde = "1.0.192" serde = "1.0.192"
serde_json = "1.0.108" serde_json = "1.0.108"
sha2 = "0.10.8"
tokio = { version = "1.34.0", features = ["full"] } tokio = { version = "1.34.0", features = ["full"] }
tracing = "0.1.40" tracing = "0.1.40"
url = "2.4.1" url = "2.4.1"

View File

@ -15,6 +15,7 @@ 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()
@ -82,21 +83,28 @@ async fn edit_article(
data: Data<DatabaseHandle>, data: Data<DatabaseHandle>,
Form(edit_article): Form<EditArticle>, Form(edit_article): Form<EditArticle>,
) -> MyResult<Json<DbArticle>> { ) -> MyResult<Json<DbArticle>> {
let article = { let original_article = {
let mut lock = data.articles.lock().unwrap();
let article = lock.get_mut(edit_article.ap_id.inner()).unwrap();
article.clone()
};
let edit = DbEdit::new(&original_article, &edit_article.new_text)?;
let updated_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.clone() article.clone()
}; };
CreateOrUpdateArticle::send_to_local_followers( CreateOrUpdateArticle::send_to_local_followers(
article.clone(), updated_article.clone(),
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
&data, &data,
) )
.await?; .await?;
Ok(Json(article)) Ok(Json(updated_article))
} }
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]

View File

@ -1,10 +1,13 @@
use crate::database::DatabaseHandle; use crate::database::DatabaseHandle;
use crate::error::Error; use crate::error::{Error, MyResult};
use crate::federation::objects::article::DbArticle; 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::create_patch;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
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.
@ -15,6 +18,21 @@ pub struct DbEdit {
pub local: bool, pub local: bool,
} }
impl DbEdit {
pub fn new(original_article: &DbArticle, updated_text: &str) -> MyResult<Self> {
let diff = create_patch(&original_article.text, updated_text);
let mut sha224 = Sha224::new();
sha224.update(diff.to_bytes());
let hash = format!("{:X}", sha224.finalize());
let edit_id = ObjectId::parse(&format!("{}/{}", original_article.ap_id, hash))?;
Ok(DbEdit {
id: edit_id,
diff: diff.to_string(),
local: true
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum EditType { pub enum EditType {
Edit, Edit,
@ -26,7 +44,6 @@ pub struct ApubEdit {
#[serde(rename = "type")] #[serde(rename = "type")]
kind: EditType, kind: EditType,
id: ObjectId<DbEdit>, id: ObjectId<DbEdit>,
article_id: ObjectId<DbArticle>,
diff: String, diff: String,
} }
@ -44,7 +61,11 @@ impl Object for DbEdit {
} }
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
todo!() Ok(ApubEdit {
kind: EditType::Edit,
id: self.id,
diff: self.diff,
})
} }
async fn verify( async fn verify(
@ -52,13 +73,17 @@ impl Object for DbEdit {
_expected_domain: &Url, _expected_domain: &Url,
_data: &Data<Self::DataType>, _data: &Data<Self::DataType>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
todo!() Ok(())
} }
async fn from_json( async fn from_json(
_json: Self::Kind, json: Self::Kind,
_data: &Data<Self::DataType>, _data: &Data<Self::DataType>,
) -> Result<Self, Self::Error> { ) -> Result<Self, Self::Error> {
todo!() Ok(Self {
id: json.id,
diff: json.diff,
local: false,
})
} }
} }

View File

@ -75,10 +75,12 @@ 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<_>>();
for e in edits.clone() { for e in edits.clone() {
// TODO: edits need a unique id to avoid pushing duplicates if !edit_ids.contains(&&e.id) {
article.edits.push(e); article.edits.push(e);
} }
}
// TODO: return value propably not needed // TODO: return value propably not needed
Ok(DbEditCollection(edits)) Ok(DbEditCollection(edits))
} }

View File

@ -153,6 +153,8 @@ async fn test_federate_article_changes() -> MyResult<()> {
}; };
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!(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 = GetArticle {