mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 12:01:08 +00:00
wip: handle edit conflicts
This commit is contained in:
parent
61682100f2
commit
920ec258bc
5 changed files with 80 additions and 17 deletions
24
src/api.rs
24
src/api.rs
|
@ -1,13 +1,15 @@
|
||||||
use crate::database::DatabaseHandle;
|
use crate::database::DatabaseHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::{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_article::UpdateArticle;
|
||||||
use crate::federation::objects::article::DbArticle;
|
use crate::federation::objects::article::{ApubArticle, DbArticle};
|
||||||
use crate::federation::objects::edit::DbEdit;
|
use crate::federation::objects::edit::DbEdit;
|
||||||
use crate::federation::objects::instance::DbInstance;
|
use crate::federation::objects::instance::{ApubInstance, 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;
|
||||||
|
use activitypub_federation::traits::Object;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use async_trait::async_trait;
|
||||||
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};
|
||||||
|
@ -21,7 +23,8 @@ pub fn api_routes() -> Router {
|
||||||
"/article",
|
"/article",
|
||||||
get(get_article).post(create_article).patch(edit_article),
|
get(get_article).post(create_article).patch(edit_article),
|
||||||
)
|
)
|
||||||
.route("/resolve_object", get(resolve_object))
|
.route("/resolve_instance", get(resolve_instance))
|
||||||
|
.route("/resolve_article", get(resolve_article))
|
||||||
.route("/instance", get(get_local_instance))
|
.route("/instance", get(get_local_instance))
|
||||||
.route("/instance/follow", post(follow_instance))
|
.route("/instance/follow", post(follow_instance))
|
||||||
}
|
}
|
||||||
|
@ -118,16 +121,23 @@ pub struct ResolveObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn resolve_object(
|
async fn resolve_instance(
|
||||||
Query(query): Query<ResolveObject>,
|
Query(query): Query<ResolveObject>,
|
||||||
data: Data<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> MyResult<Json<DbInstance>> {
|
) -> MyResult<Json<DbInstance>> {
|
||||||
let instance: DbInstance = ObjectId::from(query.id).dereference(&data).await?;
|
let instance: DbInstance = ObjectId::from(query.id).dereference(&data).await?;
|
||||||
let mut instances = data.instances.lock().unwrap();
|
|
||||||
instances.insert(instance.ap_id.inner().clone(), instance.clone());
|
|
||||||
Ok(Json(instance))
|
Ok(Json(instance))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn resolve_article(
|
||||||
|
Query(query): Query<ResolveObject>,
|
||||||
|
data: Data<DatabaseHandle>,
|
||||||
|
) -> MyResult<Json<DbArticle>> {
|
||||||
|
let article: DbArticle = ObjectId::from(query.id).dereference(&data).await?;
|
||||||
|
Ok(Json(article))
|
||||||
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn get_local_instance(data: Data<DatabaseHandle>) -> MyResult<Json<DbInstance>> {
|
async fn get_local_instance(data: Data<DatabaseHandle>) -> MyResult<Json<DbInstance>> {
|
||||||
Ok(Json(data.local_instance()))
|
Ok(Json(data.local_instance()))
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct DbInstance {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Instance {
|
pub struct ApubInstance {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: ServiceType,
|
kind: ServiceType,
|
||||||
id: ObjectId<DbInstance>,
|
id: ObjectId<DbInstance>,
|
||||||
|
@ -103,7 +103,7 @@ impl DbInstance {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Object for DbInstance {
|
impl Object for DbInstance {
|
||||||
type DataType = DatabaseHandle;
|
type DataType = DatabaseHandle;
|
||||||
type Kind = Instance;
|
type Kind = ApubInstance;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
|
@ -124,7 +124,7 @@ impl Object for DbInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
Ok(Instance {
|
Ok(ApubInstance {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: self.ap_id.clone(),
|
id: self.ap_id.clone(),
|
||||||
articles: self.articles_id.clone(),
|
articles: self.articles_id.clone(),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::database::DatabaseHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::activities::accept::Accept;
|
use crate::federation::activities::accept::Accept;
|
||||||
use crate::federation::activities::follow::Follow;
|
use crate::federation::activities::follow::Follow;
|
||||||
use crate::federation::objects::instance::{DbInstance, Instance};
|
use crate::federation::objects::instance::{ApubInstance, DbInstance};
|
||||||
|
|
||||||
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
||||||
use activitypub_federation::axum::json::FederationJson;
|
use activitypub_federation::axum::json::FederationJson;
|
||||||
|
@ -36,7 +36,7 @@ pub fn federation_routes() -> Router {
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn http_get_instance(
|
async fn http_get_instance(
|
||||||
data: Data<DatabaseHandle>,
|
data: Data<DatabaseHandle>,
|
||||||
) -> MyResult<FederationJson<WithContext<Instance>>> {
|
) -> MyResult<FederationJson<WithContext<ApubInstance>>> {
|
||||||
let db_instance = data.local_instance();
|
let db_instance = data.local_instance();
|
||||||
let json_instance = db_instance.into_json(&data).await?;
|
let json_instance = db_instance.into_json(&data).await?;
|
||||||
Ok(FederationJson(WithContext::new_default(json_instance)))
|
Ok(FederationJson(WithContext::new_default(json_instance)))
|
||||||
|
|
|
@ -114,12 +114,12 @@ pub async fn follow_instance(follow_instance: &str, followed_instance: &str) ->
|
||||||
let resolve_form = ResolveObject {
|
let resolve_form = ResolveObject {
|
||||||
id: Url::parse(&format!("http://{}", followed_instance))?,
|
id: Url::parse(&format!("http://{}", followed_instance))?,
|
||||||
};
|
};
|
||||||
let beta_instance_resolved: DbInstance =
|
let instance_resolved: DbInstance =
|
||||||
get_query(followed_instance, "resolve_object", Some(resolve_form)).await?;
|
get_query(followed_instance, "resolve_instance", Some(resolve_form)).await?;
|
||||||
|
|
||||||
// send follow
|
// send follow
|
||||||
let follow_form = FollowInstance {
|
let follow_form = FollowInstance {
|
||||||
instance_id: beta_instance_resolved.ap_id,
|
instance_id: instance_resolved.ap_id,
|
||||||
};
|
};
|
||||||
// cant use post helper because follow doesnt return json
|
// cant use post helper because follow doesnt return json
|
||||||
CLIENT
|
CLIENT
|
||||||
|
|
|
@ -112,7 +112,8 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
let resolve_object = ResolveObject {
|
let resolve_object = ResolveObject {
|
||||||
id: Url::parse(&format!("http://{}", data.hostname_alpha))?,
|
id: Url::parse(&format!("http://{}", data.hostname_alpha))?,
|
||||||
};
|
};
|
||||||
get_query::<DbInstance, _>(data.hostname_beta, "resolve_object", Some(resolve_object)).await?;
|
get_query::<DbInstance, _>(data.hostname_beta, "resolve_instance", Some(resolve_object))
|
||||||
|
.await?;
|
||||||
|
|
||||||
// get the article and compare
|
// get the article and compare
|
||||||
let get_res: DbArticle = get_query(
|
let get_res: DbArticle = get_query(
|
||||||
|
@ -210,7 +211,6 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
assert_eq!(create_res.title, get_res.title);
|
assert_eq!(create_res.title, get_res.title);
|
||||||
assert_eq!(0, get_res.edits.len());
|
assert_eq!(0, get_res.edits.len());
|
||||||
assert!(!get_res.local);
|
assert!(!get_res.local);
|
||||||
assert_eq!(create_res.text, get_res.text);
|
|
||||||
|
|
||||||
let get_res =
|
let get_res =
|
||||||
get_query::<DbArticle, _>(data.hostname_gamma, "article", Some(get_article.clone()))
|
get_query::<DbArticle, _>(data.hostname_gamma, "article", Some(get_article.clone()))
|
||||||
|
@ -250,3 +250,56 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
|
|
||||||
data.stop()
|
data.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_edit_conflict() -> MyResult<()> {
|
||||||
|
let data = TestData::start();
|
||||||
|
|
||||||
|
follow_instance(data.hostname_alpha, data.hostname_beta).await?;
|
||||||
|
|
||||||
|
// create new article
|
||||||
|
let create_form = CreateArticleData {
|
||||||
|
title: "Manu_Chao".to_string(),
|
||||||
|
};
|
||||||
|
let create_res: DbArticle = post(data.hostname_beta, "article", &create_form).await?;
|
||||||
|
assert_eq!(create_res.title, create_form.title);
|
||||||
|
assert!(create_res.local);
|
||||||
|
|
||||||
|
// alpha edits article
|
||||||
|
let edit_form = EditArticleData {
|
||||||
|
ap_id: create_res.ap_id.clone(),
|
||||||
|
new_text: "Lorem Ipsum".to_string(),
|
||||||
|
};
|
||||||
|
let edit_res: DbArticle = patch(data.hostname_alpha, "article", &edit_form).await?;
|
||||||
|
assert_eq!(edit_res.text, edit_form.new_text);
|
||||||
|
assert_eq!(edit_res.edits.len(), 1);
|
||||||
|
assert!(!edit_res.local);
|
||||||
|
assert!(edit_res.edits[0]
|
||||||
|
.id
|
||||||
|
.to_string()
|
||||||
|
.starts_with(&edit_res.ap_id.to_string()));
|
||||||
|
|
||||||
|
// fetch article to gamma
|
||||||
|
let resolve_object = ResolveObject {
|
||||||
|
id: create_res.ap_id.inner().clone(),
|
||||||
|
};
|
||||||
|
get_query::<DbArticle, _>(data.hostname_gamma, "resolve_article", Some(resolve_object)).await?;
|
||||||
|
|
||||||
|
// gamma also edits, as its not the latest version there is a conflict
|
||||||
|
// TODO: get this working
|
||||||
|
let edit_form = EditArticleData {
|
||||||
|
ap_id: create_res.ap_id,
|
||||||
|
new_text: "Ipsum Lorem".to_string(),
|
||||||
|
};
|
||||||
|
let edit_res: DbArticle = patch(data.hostname_gamma, "article", &edit_form).await?;
|
||||||
|
assert_eq!(edit_res.text, edit_form.new_text);
|
||||||
|
assert_eq!(edit_res.edits.len(), 1);
|
||||||
|
assert!(!edit_res.local);
|
||||||
|
assert!(edit_res.edits[0]
|
||||||
|
.id
|
||||||
|
.to_string()
|
||||||
|
.starts_with(&edit_res.ap_id.to_string()));
|
||||||
|
|
||||||
|
data.stop()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue