mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 08:11:08 +00:00
dont apply edit to already updated text
This commit is contained in:
parent
d224016327
commit
1539795f03
5 changed files with 50 additions and 17 deletions
|
@ -3,7 +3,7 @@ use crate::error::MyResult;
|
|||
use crate::federation::activities::create_article::CreateArticle;
|
||||
use crate::federation::activities::update_article::UpdateArticle;
|
||||
use crate::federation::objects::article::DbArticle;
|
||||
use crate::federation::objects::edit::DbEdit;
|
||||
use crate::federation::objects::edit::{DbEdit, EditVersion};
|
||||
use crate::federation::objects::instance::DbInstance;
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
|
@ -40,17 +40,17 @@ async fn create_article(
|
|||
Form(create_article): Form<CreateArticleData>,
|
||||
) -> MyResult<Json<DbArticle>> {
|
||||
let local_instance_id = data.local_instance().ap_id;
|
||||
let ap_id = Url::parse(&format!(
|
||||
let ap_id = ObjectId::parse(&format!(
|
||||
"http://{}:{}/article/{}",
|
||||
local_instance_id.inner().domain().unwrap(),
|
||||
local_instance_id.inner().port().unwrap(),
|
||||
create_article.title
|
||||
))?
|
||||
.into();
|
||||
))?;
|
||||
let article = DbArticle {
|
||||
title: create_article.title,
|
||||
text: String::new(),
|
||||
ap_id,
|
||||
latest_version: EditVersion::default(),
|
||||
edits: vec![],
|
||||
instance: local_instance_id,
|
||||
local: true,
|
||||
|
@ -87,6 +87,7 @@ async fn edit_article(
|
|||
let mut lock = data.articles.lock().unwrap();
|
||||
let article = lock.get_mut(edit_article.ap_id.inner()).unwrap();
|
||||
article.text = edit_article.new_text;
|
||||
article.latest_version = edit.version.clone();
|
||||
article.edits.push(edit.clone());
|
||||
article.clone()
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::database::{Database, DatabaseHandle};
|
|||
use crate::error::Error;
|
||||
use crate::federation::objects::instance::DbInstance;
|
||||
use activitypub_federation::config::FederationConfig;
|
||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
||||
use activitypub_federation::http_signatures::generate_actor_keypair;
|
||||
use chrono::Local;
|
||||
use std::collections::HashMap;
|
||||
|
@ -14,7 +15,7 @@ pub mod routes;
|
|||
|
||||
pub async fn federation_config(hostname: &str) -> Result<FederationConfig<DatabaseHandle>, Error> {
|
||||
let ap_id = Url::parse(&format!("http://{}", hostname))?.into();
|
||||
let articles_id = Url::parse(&format!("http://{}/all_articles", hostname))?.into();
|
||||
let articles_id = CollectionId::parse(&format!("http://{}/all_articles", hostname))?;
|
||||
let inbox = Url::parse(&format!("http://{}/inbox", hostname))?;
|
||||
let keypair = generate_actor_keypair()?;
|
||||
let local_instance = DbInstance {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::error::MyResult;
|
||||
use crate::federation::objects::edit::DbEdit;
|
||||
use crate::federation::objects::edit::{DbEdit, EditVersion};
|
||||
use crate::federation::objects::edits_collection::DbEditCollection;
|
||||
use crate::federation::objects::instance::DbInstance;
|
||||
use crate::{database::DatabaseHandle, error::Error};
|
||||
|
@ -23,14 +23,13 @@ pub struct DbArticle {
|
|||
pub instance: ObjectId<DbInstance>,
|
||||
/// List of all edits which make up this article, oldest first.
|
||||
pub edits: Vec<DbEdit>,
|
||||
pub latest_version: EditVersion,
|
||||
pub local: bool,
|
||||
}
|
||||
|
||||
impl DbArticle {
|
||||
fn edits_id(&self) -> MyResult<CollectionId<DbEditCollection>> {
|
||||
Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))
|
||||
.unwrap()
|
||||
.into())
|
||||
Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +43,7 @@ pub struct ApubArticle {
|
|||
#[serde(deserialize_with = "deserialize_one_or_many")]
|
||||
pub(crate) to: Vec<Url>,
|
||||
edits: CollectionId<DbEditCollection>,
|
||||
latest_version: EditVersion,
|
||||
content: String,
|
||||
name: String,
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ impl Object for DbArticle {
|
|||
attributed_to: self.instance.clone(),
|
||||
to: vec![public(), instance.followers_url()?],
|
||||
edits: self.edits_id()?,
|
||||
latest_version: self.latest_version,
|
||||
content: self.text,
|
||||
name: self.title,
|
||||
})
|
||||
|
@ -97,6 +98,7 @@ impl Object for DbArticle {
|
|||
instance: json.attributed_to,
|
||||
// TODO: shouldnt overwrite existing edits
|
||||
edits: vec![],
|
||||
latest_version: json.latest_version,
|
||||
local: false,
|
||||
};
|
||||
|
||||
|
@ -105,7 +107,7 @@ impl Object for DbArticle {
|
|||
lock.insert(article.ap_id.inner().clone(), article.clone());
|
||||
}
|
||||
|
||||
json.edits.dereference(&article, &data).await?;
|
||||
json.edits.dereference(&article, data).await?;
|
||||
|
||||
Ok(article)
|
||||
}
|
||||
|
|
|
@ -10,12 +10,24 @@ use sha2::Digest;
|
|||
use sha2::Sha224;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct EditVersion(String);
|
||||
|
||||
impl Default for EditVersion {
|
||||
fn default() -> Self {
|
||||
let sha224 = Sha224::new();
|
||||
let hash = format!("{:X}", sha224.finalize());
|
||||
EditVersion(hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a single change to the article.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DbEdit {
|
||||
pub id: ObjectId<DbEdit>,
|
||||
pub diff: String,
|
||||
pub article_id: ObjectId<DbArticle>,
|
||||
pub version: EditVersion,
|
||||
pub local: bool,
|
||||
}
|
||||
|
||||
|
@ -30,6 +42,7 @@ impl DbEdit {
|
|||
id: edit_id,
|
||||
diff: diff.to_string(),
|
||||
article_id: original_article.ap_id.clone(),
|
||||
version: EditVersion(hash),
|
||||
local: true,
|
||||
})
|
||||
}
|
||||
|
@ -46,7 +59,8 @@ pub struct ApubEdit {
|
|||
#[serde(rename = "type")]
|
||||
kind: EditType,
|
||||
id: ObjectId<DbEdit>,
|
||||
pub(crate) content: String,
|
||||
pub content: String,
|
||||
pub version: EditVersion,
|
||||
pub object: ObjectId<DbArticle>,
|
||||
}
|
||||
|
||||
|
@ -68,6 +82,7 @@ impl Object for DbEdit {
|
|||
kind: EditType::Edit,
|
||||
id: self.id,
|
||||
content: self.diff,
|
||||
version: self.version,
|
||||
object: self.article_id,
|
||||
})
|
||||
}
|
||||
|
@ -85,15 +100,19 @@ impl Object for DbEdit {
|
|||
id: json.id,
|
||||
diff: json.content,
|
||||
article_id: json.object,
|
||||
version: json.version,
|
||||
local: false,
|
||||
};
|
||||
let mut lock = data.articles.lock().unwrap();
|
||||
let article = lock.get_mut(edit.article_id.inner()).unwrap();
|
||||
article.edits.push(edit.clone());
|
||||
let patch = Patch::from_str(&edit.diff)?;
|
||||
// TODO: this will give wrong result if new article text is federated, and then also new
|
||||
// edit is applied. probably need to keep track of versions
|
||||
article.text = apply(&article.text, &patch)?;
|
||||
// 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.latest_version != edit.version {
|
||||
article.text = apply(&article.text, &patch)?;
|
||||
}
|
||||
|
||||
Ok(edit)
|
||||
}
|
||||
|
|
|
@ -81,6 +81,13 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
assert_eq!(0, create_res.edits.len());
|
||||
assert!(create_res.local);
|
||||
|
||||
// edit the article
|
||||
let edit_form = EditArticleData {
|
||||
ap_id: create_res.ap_id.clone(),
|
||||
new_text: "Lorem Ipsum 2".to_string(),
|
||||
};
|
||||
edit_article(data.hostname_alpha, &title, &edit_form).await?;
|
||||
|
||||
// article is not yet on beta
|
||||
let get_res = get_article(data.hostname_beta, &create_res.title).await;
|
||||
assert!(get_res.is_err());
|
||||
|
@ -96,8 +103,8 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
let get_res = get_article(data.hostname_beta, &create_res.title).await?;
|
||||
assert_eq!(create_res.ap_id, get_res.ap_id);
|
||||
assert_eq!(title, get_res.title);
|
||||
assert_eq!(0, get_res.edits.len());
|
||||
assert!(get_res.text.is_empty());
|
||||
assert_eq!(1, get_res.edits.len());
|
||||
assert_eq!(edit_form.new_text, get_res.text);
|
||||
assert!(!get_res.local);
|
||||
|
||||
data.stop()
|
||||
|
@ -231,7 +238,8 @@ async fn test_edit_conflict() -> MyResult<()> {
|
|||
.to_string()
|
||||
.starts_with(&edit_res.ap_id.to_string()));
|
||||
|
||||
// gamma also edits, as its not the latest version there is a conflict
|
||||
// gamma also edits, as its not the latest version there is a conflict. local version should
|
||||
// not be updated with this conflicting version, instead user needs to handle the conflict
|
||||
let edit_form = EditArticleData {
|
||||
ap_id: create_res.ap_id,
|
||||
new_text: "aaaa".to_string(),
|
||||
|
@ -241,5 +249,7 @@ async fn test_edit_conflict() -> MyResult<()> {
|
|||
assert_eq!(0, edit_res.edits.len());
|
||||
assert!(!edit_res.local);
|
||||
|
||||
// TODO: need to federate the conflict as `Reject` and then resolve it
|
||||
|
||||
data.stop()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue