some cleanup

This commit is contained in:
Felix Ableitner 2023-12-05 12:54:38 +01:00
parent 132d9c8389
commit 5c969078a8
9 changed files with 145 additions and 83 deletions

View File

@ -273,11 +273,10 @@ async fn fork_article(
let article = DbArticle::create(&form, &data.db_connection)?; let article = DbArticle::create(&form, &data.db_connection)?;
// copy edits to new article // copy edits to new article
// TODO: convert to sql // this could also be done in sql
let edits = DbEdit::read_for_article(&original_article, &data.db_connection)?; let edits = DbEdit::read_for_article(&original_article, &data.db_connection)?;
for e in edits { for e in edits {
let ap_id = DbEditForm::generate_ap_id(&article, &e.hash)?; let ap_id = DbEditForm::generate_ap_id(&article, &e.hash)?;
// TODO: id gives db unique violation
let form = DbEditForm { let form = DbEditForm {
ap_id, ap_id,
diff: e.diff, diff: e.diff,
@ -285,7 +284,7 @@ async fn fork_article(
hash: e.hash, hash: e.hash,
previous_version_id: e.previous_version_id, previous_version_id: e.previous_version_id,
}; };
dbg!(DbEdit::create(&form, &data.db_connection))?; DbEdit::create(&form, &data.db_connection)?;
} }
CreateArticle::send_to_followers(article.clone(), &data).await?; CreateArticle::send_to_followers(article.clone(), &data).await?;

View File

@ -1,6 +1,6 @@
use crate::database::edit::DbEdit; use crate::database::edit::DbEdit;
use crate::database::schema::article; use crate::database::schema::{article, edit};
use crate::error::MyResult; use crate::error::MyResult;
use crate::federation::objects::edits_collection::DbEditCollection; use crate::federation::objects::edits_collection::DbEditCollection;
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
@ -76,7 +76,7 @@ impl DbArticle {
.get_result::<Self>(conn.deref_mut())?) .get_result::<Self>(conn.deref_mut())?)
} }
pub fn read(id: i32, conn: &Mutex<PgConnection>) -> MyResult<DbArticle> { pub fn read(id: i32, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(article::table.find(id).get_result(conn.deref_mut())?) Ok(article::table.find(id).get_result(conn.deref_mut())?)
} }
@ -98,14 +98,14 @@ impl DbArticle {
pub fn read_from_ap_id( pub fn read_from_ap_id(
ap_id: &ObjectId<DbArticle>, ap_id: &ObjectId<DbArticle>,
conn: &Mutex<PgConnection>, conn: &Mutex<PgConnection>,
) -> MyResult<DbArticle> { ) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(article::table Ok(article::table
.filter(article::dsl::ap_id.eq(ap_id)) .filter(article::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_local_title(title: &str, conn: &Mutex<PgConnection>) -> MyResult<DbArticle> { pub fn read_local_title(title: &str, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(article::table Ok(article::table
.filter(article::dsl::title.eq(title)) .filter(article::dsl::title.eq(title))
@ -113,14 +113,14 @@ impl DbArticle {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_all_local(conn: &Mutex<PgConnection>) -> MyResult<Vec<DbArticle>> { pub fn read_all_local(conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(article::table Ok(article::table
.filter(article::dsl::local.eq(true)) .filter(article::dsl::local.eq(true))
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
pub fn search(query: &str, conn: &Mutex<PgConnection>) -> MyResult<Vec<DbArticle>> { pub fn search(query: &str, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
let replaced = query let replaced = query
.replace('%', "\\%") .replace('%', "\\%")
@ -135,10 +135,16 @@ impl DbArticle {
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
// TODO: shouldnt have to read all edits from db
pub fn latest_edit_version(&self, conn: &Mutex<PgConnection>) -> MyResult<EditVersion> { pub fn latest_edit_version(&self, conn: &Mutex<PgConnection>) -> MyResult<EditVersion> {
let edits: Vec<DbEdit> = DbEdit::read_for_article(self, conn)?; let mut conn = conn.lock().unwrap();
match edits.last().map(|e| e.hash.clone()) { let latest_version: Option<EditVersion> = edit::table
.filter(edit::dsl::article_id.eq(self.id))
.order_by(edit::dsl::id.desc())
.limit(1)
.select(edit::dsl::hash)
.get_result(conn.deref_mut())
.ok();
match latest_version {
Some(latest_version) => Ok(latest_version), Some(latest_version) => Ok(latest_version),
None => Ok(EditVersion::default()), None => Ok(EditVersion::default()),
} }

View File

@ -52,6 +52,7 @@ impl DbConflict {
.values(form) .values(form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn list(conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn list(conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(conflict::table.get_results(conn.deref_mut())?) Ok(conflict::table.get_results(conn.deref_mut())?)
@ -60,7 +61,6 @@ impl DbConflict {
/// Delete a merge conflict after it is resolved. /// Delete a merge conflict after it is resolved.
pub fn delete(id: EditVersion, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn delete(id: EditVersion, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
// TODO: should throw error on invalid id param
Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?) Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?)
} }

View File

@ -79,6 +79,7 @@ impl DbEdit {
.set(form) .set(form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read(version: &EditVersion, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read(version: &EditVersion, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(edit::table Ok(edit::table
@ -86,6 +87,13 @@ impl DbEdit {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_from_ap_id(ap_id: &ObjectId<DbEdit>, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap();
Ok(edit::table
.filter(edit::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?)
}
pub fn read_for_article( pub fn read_for_article(
article: &DbArticle, article: &DbArticle,
conn: &Mutex<PgConnection>, conn: &Mutex<PgConnection>,

View File

@ -1,14 +1,10 @@
use crate::database::schema::{instance, instance_follow}; use crate::database::schema::{instance, instance_follow};
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::{Error, MyResult}; use crate::error::MyResult;
use crate::federation::objects::articles_collection::DbArticleCollection; use crate::federation::objects::articles_collection::DbArticleCollection;
use activitypub_federation::activity_sending::SendActivityTask;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use activitypub_federation::protocol::context::WithContext;
use activitypub_federation::traits::ActivityHandler;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{ use diesel::{
@ -19,8 +15,6 @@ use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex; use std::sync::Mutex;
use tracing::warn;
use url::Url;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)]
#[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))]
@ -59,62 +53,6 @@ pub struct InstanceView {
} }
impl DbInstance { impl DbInstance {
pub fn followers_url(&self) -> MyResult<Url> {
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
}
pub fn follower_ids(&self, data: &Data<MyDataHandle>) -> MyResult<Vec<Url>> {
Ok(DbInstance::read_followers(self.id, &data.db_connection)?
.into_iter()
.map(|f| f.ap_id.into())
.collect())
}
pub async fn send_to_followers<Activity>(
&self,
activity: Activity,
extra_recipients: Vec<DbInstance>,
data: &Data<MyDataHandle>,
) -> Result<(), <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
<Activity as ActivityHandler>::Error: From<Error>,
{
let mut inboxes: Vec<_> = DbInstance::read_followers(self.id, &data.db_connection)?
.iter()
.map(|f| Url::parse(&f.inbox_url).unwrap())
.collect();
inboxes.extend(
extra_recipients
.into_iter()
.map(|i| Url::parse(&i.inbox_url).unwrap()),
);
self.send(activity, inboxes, data).await?;
Ok(())
}
pub async fn send<Activity>(
&self,
activity: Activity,
recipients: Vec<Url>,
data: &Data<MyDataHandle>,
) -> Result<(), <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
{
let activity = WithContext::new_default(activity);
let sends = SendActivityTask::prepare(&activity, self, recipients, data).await?;
for send in sends {
let send = send.sign_and_send(data).await;
if let Err(e) = send {
warn!("Failed to send activity {:?}: {e}", activity);
}
}
Ok(())
}
pub fn create(form: &DbInstanceForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(form: &DbInstanceForm, conn: &Mutex<PgConnection>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = conn.lock().unwrap();
Ok(insert_into(instance::table) Ok(insert_into(instance::table)

View File

@ -33,10 +33,10 @@ impl Object for DbEdit {
type Error = Error; type Error = Error;
async fn read_from_id( async fn read_from_id(
_object_id: Url, object_id: Url,
_data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> { ) -> Result<Option<Self>, Self::Error> {
todo!() Ok(DbEdit::read_from_ap_id(&object_id.into(), data).ok())
} }
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> {

View File

@ -1,9 +1,12 @@
use crate::database::instance::{DbInstance, DbInstanceForm}; use crate::database::instance::{DbInstance, DbInstanceForm};
use crate::database::MyDataHandle; use crate::database::MyDataHandle;
use crate::error::Error; use crate::error::{Error, MyResult};
use crate::federation::objects::articles_collection::DbArticleCollection; use crate::federation::objects::articles_collection::DbArticleCollection;
use activitypub_federation::activity_sending::SendActivityTask;
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
use activitypub_federation::kinds::actor::ServiceType; use activitypub_federation::kinds::actor::ServiceType;
use activitypub_federation::protocol::context::WithContext;
use activitypub_federation::traits::ActivityHandler;
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
@ -13,6 +16,7 @@ use activitypub_federation::{
use chrono::{DateTime, Local, Utc}; use chrono::{DateTime, Local, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use tracing::log::warn;
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -26,6 +30,64 @@ pub struct ApubInstance {
public_key: PublicKey, public_key: PublicKey,
} }
impl DbInstance {
pub fn followers_url(&self) -> MyResult<Url> {
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
}
pub fn follower_ids(&self, data: &Data<MyDataHandle>) -> MyResult<Vec<Url>> {
Ok(DbInstance::read_followers(self.id, &data.db_connection)?
.into_iter()
.map(|f| f.ap_id.into())
.collect())
}
pub async fn send_to_followers<Activity>(
&self,
activity: Activity,
extra_recipients: Vec<DbInstance>,
data: &Data<MyDataHandle>,
) -> Result<(), <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
<Activity as ActivityHandler>::Error: From<Error>,
{
let mut inboxes: Vec<_> = DbInstance::read_followers(self.id, &data.db_connection)?
.iter()
.map(|f| Url::parse(&f.inbox_url).unwrap())
.collect();
inboxes.extend(
extra_recipients
.into_iter()
.map(|i| Url::parse(&i.inbox_url).unwrap()),
);
self.send(activity, inboxes, data).await?;
Ok(())
}
pub async fn send<Activity>(
&self,
activity: Activity,
recipients: Vec<Url>,
data: &Data<MyDataHandle>,
) -> Result<(), <Activity as ActivityHandler>::Error>
where
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
{
let activity = WithContext::new_default(activity);
let sends = SendActivityTask::prepare(&activity, self, recipients, data).await?;
for send in sends {
let send = send.sign_and_send(data).await;
if let Err(e) = send {
warn!("Failed to send activity {:?}: {e}", activity);
}
}
Ok(())
}
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl Object for DbInstance { impl Object for DbInstance {
type DataType = MyDataHandle; type DataType = MyDataHandle;

View File

@ -37,3 +37,54 @@ pub fn generate_article_version(edits: &Vec<DbEdit>, version: &EditVersion) -> M
} }
Err(anyhow!("failed to generate article version").into()) Err(anyhow!("failed to generate article version").into())
} }
#[cfg(test)]
mod test {
use super::*;
use activitypub_federation::fetch::object_id::ObjectId;
use diffy::create_patch;
fn create_edits() -> MyResult<Vec<DbEdit>> {
let generate_edit = |a, b| -> MyResult<DbEdit> {
let diff = create_patch(a, b).to_string();
Ok(DbEdit {
id: 0,
hash: EditVersion::new(&diff)?,
ap_id: ObjectId::parse("http://example.com")?,
diff,
article_id: 0,
previous_version_id: Default::default(),
})
};
Ok([
generate_edit("", "test\n")?,
generate_edit("test\n", "sda\n")?,
generate_edit("sda\n", "123\n")?,
]
.to_vec())
}
#[test]
fn test_generate_article_version() -> MyResult<()> {
let edits = create_edits()?;
let generated = generate_article_version(&edits, &edits[1].hash)?;
assert_eq!("sda\n", generated);
Ok(())
}
#[test]
fn test_generate_invalid_version() -> MyResult<()> {
let edits = create_edits()?;
let generated = generate_article_version(&edits, &EditVersion::new("invalid")?);
assert!(generated.is_err());
Ok(())
}
#[test]
fn test_generate_first_version() -> MyResult<()> {
let edits = create_edits()?;
let generated = generate_article_version(&edits, &EditVersion::default())?;
assert_eq!("", generated);
Ok(())
}
}

View File

@ -16,7 +16,6 @@ use fediwiki::database::instance::{DbInstance, InstanceView};
use pretty_assertions::{assert_eq, assert_ne}; use pretty_assertions::{assert_eq, assert_ne};
use url::Url; use url::Url;
// TODO: can run tests in parallel if we use different ports
#[tokio::test] #[tokio::test]
async fn test_create_read_and_edit_article() -> MyResult<()> { async fn test_create_read_and_edit_article() -> MyResult<()> {
let data = TestData::start(); let data = TestData::start();
@ -354,8 +353,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
}; };
let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?; let edit_res = edit_article(&data.gamma.hostname, &edit_form).await?;
assert_ne!(edit_form.new_text, edit_res.article.text); assert_ne!(edit_form.new_text, edit_res.article.text);
// TODO assert_eq!(1, edit_res.edits.len());
//assert_eq!(2, edit_res.edits.len());
assert!(!edit_res.article.local); assert!(!edit_res.article.local);
let conflicts: Vec<ApiConflict> = let conflicts: Vec<ApiConflict> =