diff --git a/Cargo.lock b/Cargo.lock index f205217..c213cdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,12 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -486,6 +492,51 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diesel" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" +dependencies = [ + "bitflags 2.4.1", + "byteorder", + "diesel_derives", + "itoa", + "pq-sys", +] + +[[package]] +name = "diesel-derive-newtype" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7267437d5b12df60ae29bd97f8d120f1c3a6272d6f213551afa56bbb2ecfbb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "diesel_derives" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" +dependencies = [ + "diesel_table_macro_syntax", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" +dependencies = [ + "syn 2.0.39", +] + [[package]] name = "diffy" version = "0.3.0" @@ -615,6 +666,8 @@ dependencies = [ "axum", "axum-macros", "chrono", + "diesel", + "diesel-derive-newtype", "diffy", "enum_delegate", "env_logger", @@ -1408,6 +1461,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pq-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0052426df997c0cbd30789eb44ca097e3541717a7b8fa36b1c464ee7edebd" +dependencies = [ + "vcpkg", +] + [[package]] name = "proc-macro2" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index b806626..3b37342 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ async-trait = "0.1.74" axum = "0.6.20" axum-macros = "0.3.8" chrono = { version = "0.4.31", features = ["serde"] } +diesel = {version = "2.1.4", features = ["postgres"] } +diesel-derive-newtype = "2.1.0" diffy = "0.3.0" enum_delegate = "0.2.0" env_logger = { version = "0.10.1", default-features = false } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..85fd363 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/database/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId"] + +[migrations_directory] +dir = "migrations" diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/2023-11-28-150402_article/down.sql b/migrations/2023-11-28-150402_article/down.sql new file mode 100644 index 0000000..f5b240a --- /dev/null +++ b/migrations/2023-11-28-150402_article/down.sql @@ -0,0 +1,2 @@ +drop table edit; +drop table article; \ No newline at end of file diff --git a/migrations/2023-11-28-150402_article/up.sql b/migrations/2023-11-28-150402_article/up.sql new file mode 100644 index 0000000..675fecf --- /dev/null +++ b/migrations/2023-11-28-150402_article/up.sql @@ -0,0 +1,18 @@ +create table article ( + id serial primary key, + title text not null, + text text not null, + ap_id varchar(255) not null, + instance_id varchar(255) not null, + latest_version text not null, + local bool not null +); + +create table edit ( + id serial primary key, + ap_id varchar(255) not null, + diff text not null, + article_id int REFERENCES article ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + version text not null, + local bool not null +) \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 52eb81e..40f695e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,9 +1,10 @@ -use crate::database::{DatabaseHandle, DbConflict}; +use crate::database::article::{DbArticle, DbArticleForm}; +use crate::database::dburl::DbUrl; +use crate::database::edit::{DbEdit, EditVersion}; +use crate::database::{DbConflict, MyDataHandle}; use crate::error::MyResult; use crate::federation::activities::create_article::CreateArticle; use crate::federation::activities::submit_article_update; -use crate::federation::objects::article::DbArticle; -use crate::federation::objects::edit::EditVersion; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_article_version; use activitypub_federation::config::Data; @@ -42,7 +43,7 @@ pub struct CreateArticleData { /// Create a new article with empty text, and federate it to followers. #[debug_handler] async fn create_article( - data: Data, + data: Data, Form(create_article): Form, ) -> MyResult> { { @@ -55,26 +56,23 @@ async fn create_article( } } - let local_instance_id = data.local_instance().ap_id; - let ap_id = ObjectId::parse(&format!( + let instance_id: DbUrl = data.local_instance().ap_id.into(); + let ap_id = Url::parse(&format!( "http://{}:{}/article/{}", - local_instance_id.inner().domain().unwrap(), - local_instance_id.inner().port().unwrap(), + instance_id.domain().unwrap(), + instance_id.port().unwrap(), create_article.title - ))?; - let article = DbArticle { + ))? + .into(); + let form = DbArticleForm { title: create_article.title, text: String::new(), ap_id, - latest_version: EditVersion::default(), - edits: vec![], - instance: local_instance_id, + latest_version: Default::default(), + instance_id, local: true, }; - { - let mut articles = data.articles.lock().unwrap(); - articles.insert(article.ap_id.inner().clone(), article.clone()); - } + let article = DbArticle::create(&form, &data.db_connection)?; CreateArticle::send_to_followers(article.clone(), &data).await?; @@ -114,7 +112,7 @@ pub struct ApiConflict { /// Conflicts are stored in the database so they can be retrieved later from `/api/v3/edit_conflicts`. #[debug_handler] async fn edit_article( - data: Data, + data: Data, Form(edit_form): Form, ) -> MyResult>> { // resolve conflict if any @@ -138,14 +136,14 @@ async fn edit_article( } else { // There have been other changes since this edit was initiated. Get the common ancestor // version and generate a diff to find out what exactly has changed. - let ancestor = - generate_article_version(&original_article.edits, &edit_form.previous_version)?; + let edits = DbEdit::for_article(original_article.id, &data.db_connection)?; + let ancestor = generate_article_version(&edits, &edit_form.previous_version)?; let patch = create_patch(&ancestor, &edit_form.new_text); let db_conflict = DbConflict { id: random(), diff: patch.to_string(), - article_id: original_article.ap_id.clone(), + article_id: original_article.ap_id.clone().into(), previous_version: edit_form.previous_version, }; { @@ -158,23 +156,16 @@ async fn edit_article( #[derive(Deserialize, Serialize, Clone)] pub struct GetArticleData { - pub ap_id: ObjectId, + pub id: i32, } /// Retrieve an article by ID. It must already be stored in the local database. #[debug_handler] async fn get_article( Query(query): Query, - data: Data, + data: Data, ) -> MyResult> { - let articles = data.articles.lock().unwrap(); - let article = articles - .iter() - .find(|a| a.1.ap_id == query.ap_id) - .ok_or(anyhow!("not found"))? - .1 - .clone(); - Ok(Json(article)) + Ok(Json(DbArticle::read(query.id, &data.db_connection)?)) } #[derive(Deserialize, Serialize)] @@ -187,7 +178,7 @@ pub struct ResolveObject { #[debug_handler] async fn resolve_instance( Query(query): Query, - data: Data, + data: Data, ) -> MyResult> { let instance: DbInstance = ObjectId::from(query.id).dereference(&data).await?; Ok(Json(instance)) @@ -198,7 +189,7 @@ async fn resolve_instance( #[debug_handler] async fn resolve_article( Query(query): Query, - data: Data, + data: Data, ) -> MyResult> { let article: DbArticle = ObjectId::from(query.id).dereference(&data).await?; Ok(Json(article)) @@ -206,7 +197,7 @@ async fn resolve_article( /// Retrieve the local instance info. #[debug_handler] -async fn get_local_instance(data: Data) -> MyResult> { +async fn get_local_instance(data: Data) -> MyResult> { Ok(Json(data.local_instance())) } @@ -219,7 +210,7 @@ pub struct FollowInstance { /// updated articles. #[debug_handler] async fn follow_instance( - data: Data, + data: Data, Form(query): Form, ) -> MyResult<()> { let instance = query.instance_id.dereference(&data).await?; @@ -229,7 +220,7 @@ async fn follow_instance( /// Get a list of all unresolved edit conflicts. #[debug_handler] -async fn edit_conflicts(data: Data) -> MyResult>> { +async fn edit_conflicts(data: Data) -> MyResult>> { let conflicts = { data.conflicts.lock().unwrap().to_vec() }; let conflicts: Vec = try_join_all(conflicts.into_iter().map(|c| { let data = data.reset_request_count(); @@ -253,7 +244,7 @@ pub struct SearchArticleData { #[debug_handler] async fn search_article( Query(query): Query, - data: Data, + data: Data, ) -> MyResult>> { let articles = data.articles.lock().unwrap(); let article = articles @@ -277,7 +268,7 @@ pub struct ForkArticleData { /// how an article should be edited. #[debug_handler] async fn fork_article( - data: Data, + data: Data, Form(fork_form): Form, ) -> MyResult> { let article = { @@ -296,28 +287,27 @@ async fn fork_article( .clone() }; - let local_instance_id = data.local_instance().ap_id; - let ap_id = ObjectId::parse(&format!( + let instance_id: DbUrl = data.local_instance().ap_id.into(); + let ap_id = Url::parse(&format!( "http://{}:{}/article/{}", - local_instance_id.inner().domain().unwrap(), - local_instance_id.inner().port().unwrap(), + instance_id.domain().unwrap(), + instance_id.port().unwrap(), original_article.title - ))?; - let forked_article = DbArticle { + ))? + .into(); + let form = DbArticleForm { title: original_article.title.clone(), text: original_article.text.clone(), ap_id, - latest_version: original_article.latest_version.clone(), - edits: original_article.edits.clone(), - instance: local_instance_id, + latest_version: original_article.latest_version.0.clone(), + instance_id, local: true, }; - { - let mut articles = data.articles.lock().unwrap(); - articles.insert(forked_article.ap_id.inner().clone(), forked_article.clone()); - } + let article = DbArticle::create(&form, &data.db_connection)?; - CreateArticle::send_to_followers(forked_article.clone(), &data).await?; + // TODO: need to copy edits separately with db query - Ok(Json(forked_article)) + CreateArticle::send_to_followers(article.clone(), &data).await?; + + Ok(Json(article)) } diff --git a/src/database/article.rs b/src/database/article.rs new file mode 100644 index 0000000..8d82754 --- /dev/null +++ b/src/database/article.rs @@ -0,0 +1,78 @@ +use crate::database::dburl::DbUrl; +use crate::database::edit::EditVersion; +use crate::database::schema::article; +use crate::error::MyResult; +use crate::federation::objects::edits_collection::DbEditCollection; +use activitypub_federation::fetch::collection_id::CollectionId; +use diesel::pg::PgConnection; +use diesel::ExpressionMethods; +use diesel::{ + insert_into, AsChangeset, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl, + Selectable, +}; +use serde::{Deserialize, Serialize}; +use std::ops::DerefMut; +use std::sync::Mutex; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)] +#[diesel(table_name = article, check_for_backend(diesel::pg::Pg))] +pub struct DbArticle { + pub id: i32, + pub title: String, + pub text: String, + pub ap_id: DbUrl, + pub instance_id: DbUrl, + /// List of all edits which make up this article, oldest first. + // TODO + //pub edits: Vec, + pub latest_version: EditVersion, + pub local: bool, +} + +#[derive(Debug, Clone, Insertable, AsChangeset)] +#[diesel(table_name = article, check_for_backend(diesel::pg::Pg))] +pub struct DbArticleForm { + pub title: String, + pub text: String, + pub ap_id: DbUrl, + // TODO: change to foreign key + pub instance_id: DbUrl, + // TODO: instead of this we can use latest entry in edits table + pub latest_version: String, + pub local: bool, +} + +impl DbArticle { + pub fn edits_id(&self) -> MyResult> { + Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?) + } + + pub fn create(form: &DbArticleForm, conn: &Mutex) -> MyResult { + let mut conn = conn.lock().unwrap().deref_mut(); + Ok(insert_into(article::table) + .values(form) + .on_conflict(article::dsl::ap_id) + .do_update() + .set(form) + .get_result(conn)?) + } + + pub fn update_text(id: i32, text: &str, conn: &Mutex) -> MyResult { + let mut conn = conn.lock().unwrap(); + Ok(diesel::update(article::dsl::article.find(id)) + .set(article::dsl::text.eq(text)) + .get_result::(&mut conn)?) + } + + pub fn read(id: i32, conn: &Mutex) -> MyResult { + let mut conn = conn.lock().unwrap(); + Ok(article::table.find(id).get_result(&mut conn)?) + } + + pub fn read_from_ap_id(ap_id: &DbUrl, conn: &Mutex) -> MyResult { + let mut conn = conn.lock().unwrap(); + Ok(article::table + .filter(article::dsl::ap_id.eq(ap_id)) + .get_result(&mut conn)?) + } +} diff --git a/src/database/dburl.rs b/src/database/dburl.rs new file mode 100644 index 0000000..0931038 --- /dev/null +++ b/src/database/dburl.rs @@ -0,0 +1,96 @@ +use activitypub_federation::fetch::collection_id::CollectionId; +use activitypub_federation::fetch::object_id::ObjectId; +use activitypub_federation::traits::{Collection, Object}; +use diesel::backend::Backend; +use diesel::deserialize::FromSql; +use diesel::pg::Pg; +use diesel::{AsExpression, FromSqlRow}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::ops::Deref; +use url::Url; + +/// Copied from lemmy, could be moved into common library +#[repr(transparent)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash, AsExpression, FromSqlRow)] +#[diesel(sql_type = diesel::sql_types::Text)] +pub struct DbUrl(pub(crate) Box); + +// TODO: Lemmy doesnt need this, but for some reason derive fails to generate it +impl FromSql for DbUrl { + fn from_sql(bytes: ::RawValue<'_>) -> diesel::deserialize::Result { + todo!() + } +} + +impl Display for DbUrl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.clone().0.fmt(f) + } +} + +// the project doesnt compile with From +#[allow(clippy::from_over_into)] +impl Into for Url { + fn into(self) -> DbUrl { + DbUrl(Box::new(self)) + } +} +#[allow(clippy::from_over_into)] +impl Into for DbUrl { + fn into(self) -> Url { + *self.0 + } +} + +impl From for ObjectId +where + T: Object + Send + 'static, + for<'de2> ::Kind: Deserialize<'de2>, +{ + fn from(value: DbUrl) -> Self { + let url: Url = value.into(); + ObjectId::from(url) + } +} + +impl From for CollectionId +where + T: Collection + Send + 'static, + for<'de2> ::Kind: Deserialize<'de2>, +{ + fn from(value: DbUrl) -> Self { + let url: Url = value.into(); + CollectionId::from(url) + } +} + +impl From> for DbUrl +where + T: Collection, + for<'de2> ::Kind: Deserialize<'de2>, +{ + fn from(value: CollectionId) -> Self { + let url: Url = value.into(); + url.into() + } +} + +impl From> for DbUrl +where + T: Object, + for<'de2> ::Kind: Deserialize<'de2>, +{ + fn from(value: ObjectId) -> Self { + let url: Url = value.into(); + url.into() + } +} + +impl Deref for DbUrl { + type Target = Url; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/src/database/edit.rs b/src/database/edit.rs new file mode 100644 index 0000000..bc94ad6 --- /dev/null +++ b/src/database/edit.rs @@ -0,0 +1,86 @@ +use crate::database::article::DbArticle; +use crate::database::dburl::DbUrl; +use crate::database::schema::edit; +use crate::error::MyResult; +use activitypub_federation::fetch::object_id::ObjectId; +use diesel::ExpressionMethods; +use diesel::{ + insert_into, AsChangeset, Identifiable, Insertable, PgConnection, QueryDsl, Queryable, + RunQueryDsl, Selectable, +}; +use diesel_derive_newtype::DieselNewType; +use diffy::create_patch; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha224}; +use std::sync::Mutex; + +/// Represents a single change to the article. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)] +#[diesel(table_name = edit, check_for_backend(diesel::pg::Pg))] +pub struct DbEdit { + pub id: i32, + pub ap_id: DbUrl, + pub diff: String, + pub article_id: i32, + pub version: EditVersion, + // TODO: there is already `local` field on article, do we need this? + pub local: bool, +} + +#[derive(Debug, Clone, Insertable, AsChangeset)] +#[diesel(table_name = edit, check_for_backend(diesel::pg::Pg))] +pub struct DbEditForm { + pub ap_id: DbUrl, + pub diff: String, + pub article_id: i32, + pub version: EditVersion, + pub local: bool, +} + +impl DbEditForm { + pub fn new(original_article: &DbArticle, updated_text: &str) -> MyResult { + 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(DbEditForm { + ap_id: edit_id.into(), + diff: diff.to_string(), + article_id: original_article.ap_id.clone(), + version: EditVersion(hash), + local: true, + }) + } +} + +impl DbEdit { + pub fn create(form: &DbEditForm, conn: &Mutex) -> MyResult { + let mut conn = conn.lock().unwrap(); + Ok(insert_into(edit::table) + .values(form) + .on_conflict(edit::dsl::ap_id) + .do_update() + .set(form) + .get_result(&mut conn)?) + } + + pub fn for_article(id: i32, conn: &Mutex) -> MyResult> { + let mut conn = conn.lock().unwrap(); + Ok(edit::table + .filter(edit::dsl::id.eq(id)) + .order_by(edit::dsl::id.asc()) + .get_results(&mut conn)?) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, DieselNewType)] +pub struct EditVersion(pub String); + +impl Default for EditVersion { + fn default() -> Self { + let sha224 = Sha224::new(); + let hash = format!("{:X}", sha224.finalize()); + EditVersion(hash) + } +} diff --git a/src/database.rs b/src/database/mod.rs similarity index 72% rename from src/database.rs rename to src/database/mod.rs index 8c106be..4001edc 100644 --- a/src/database.rs +++ b/src/database/mod.rs @@ -1,26 +1,48 @@ use crate::api::ApiConflict; +use crate::database::article::DbArticle; +use crate::database::edit::DbEdit; use crate::error::MyResult; use crate::federation::activities::submit_article_update; -use crate::federation::objects::article::DbArticle; -use crate::federation::objects::edit::EditVersion; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_article_version; use activitypub_federation::config::Data; use activitypub_federation::fetch::object_id::ObjectId; +use diesel::{Identifiable, PgConnection, QueryDsl}; use diffy::{apply, merge, Patch}; +use edit::EditVersion; use std::collections::HashMap; +use std::ops::Deref; use std::sync::{Arc, Mutex}; use url::Url; -pub type DatabaseHandle = Arc; +pub mod article; +pub mod dburl; +pub mod edit; +mod schema; -pub struct Database { +#[derive(Clone)] +pub struct MyData { + pub db_connection: Arc>, + pub fake_db: Arc, +} + +impl Deref for MyData { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.fake_db + } +} +pub type MyDataHandle = MyData; + +pub struct FakeDatabase { pub instances: Mutex>, + // TODO: remove this pub articles: Mutex>, pub conflicts: Mutex>, } -impl Database { +impl FakeDatabase { pub fn local_instance(&self) -> DbInstance { let lock = self.instances.lock().unwrap(); lock.iter().find(|i| i.1.local).unwrap().1.clone() @@ -38,7 +60,7 @@ pub struct DbConflict { impl DbConflict { pub async fn to_api_conflict( &self, - data: &Data, + data: &Data, ) -> MyResult> { let original_article = { let mut lock = data.articles.lock().unwrap(); @@ -47,7 +69,8 @@ impl DbConflict { }; // create common ancestor version - let ancestor = generate_article_version(&original_article.edits, &self.previous_version)?; + let edits = DbEdit::for_article(original_article.id, &data.db_connection)?; + let ancestor = generate_article_version(&edits, &self.previous_version)?; let patch = Patch::from_str(&self.diff)?; // apply self.diff to ancestor to get `ours` @@ -67,7 +90,7 @@ impl DbConflict { Ok(Some(ApiConflict { id: self.id, three_way_merge, - article_id: original_article.ap_id.clone(), + article_id: original_article.ap_id.into(), previous_version: original_article.latest_version, })) } diff --git a/src/database/schema.rs b/src/database/schema.rs new file mode 100644 index 0000000..6c7e5e3 --- /dev/null +++ b/src/database/schema.rs @@ -0,0 +1,31 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + article (id) { + id -> Int4, + title -> Text, + text -> Text, + #[max_length = 255] + ap_id -> Varchar, + #[max_length = 255] + instance_id -> Varchar, + latest_version -> Text, + local -> Bool, + } +} + +diesel::table! { + edit (id) { + id -> Int4, + #[max_length = 255] + ap_id -> Varchar, + diff -> Text, + article_id -> Int4, + version -> Text, + local -> Bool, + } +} + +diesel::joinable!(edit -> article (article_id)); + +diesel::allow_tables_to_appear_in_same_query!(article, edit,); diff --git a/src/federation/activities/accept.rs b/src/federation/activities/accept.rs index 6bd3934..b422dc1 100644 --- a/src/federation/activities/accept.rs +++ b/src/federation/activities/accept.rs @@ -1,7 +1,7 @@ use crate::error::MyResult; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_activity_id; -use crate::{database::DatabaseHandle, federation::activities::follow::Follow}; +use crate::{database::MyDataHandle, federation::activities::follow::Follow}; use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler, }; @@ -32,7 +32,7 @@ impl Accept { #[async_trait::async_trait] impl ActivityHandler for Accept { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { diff --git a/src/federation/activities/create_article.rs b/src/federation/activities/create_article.rs index fdcc582..d36d99b 100644 --- a/src/federation/activities/create_article.rs +++ b/src/federation/activities/create_article.rs @@ -1,6 +1,6 @@ -use crate::database::DatabaseHandle; +use crate::database::{article::DbArticle, MyDataHandle}; use crate::error::MyResult; -use crate::federation::objects::article::{ApubArticle, DbArticle}; +use crate::federation::objects::article::ApubArticle; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_activity_id; use activitypub_federation::kinds::activity::CreateType; @@ -26,10 +26,7 @@ pub struct CreateArticle { } impl CreateArticle { - pub async fn send_to_followers( - article: DbArticle, - data: &Data, - ) -> MyResult<()> { + pub async fn send_to_followers(article: DbArticle, data: &Data) -> MyResult<()> { let local_instance = data.local_instance(); let object = article.clone().into_json(data).await?; let id = generate_activity_id(local_instance.ap_id.inner())?; @@ -48,7 +45,7 @@ impl CreateArticle { } #[async_trait::async_trait] impl ActivityHandler for CreateArticle { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { diff --git a/src/federation/activities/follow.rs b/src/federation/activities/follow.rs index de156d9..f5c5095 100644 --- a/src/federation/activities/follow.rs +++ b/src/federation/activities/follow.rs @@ -1,8 +1,6 @@ use crate::error::MyResult; use crate::federation::objects::instance::DbInstance; -use crate::{ - database::DatabaseHandle, federation::activities::accept::Accept, generate_activity_id, -}; +use crate::{database::MyDataHandle, federation::activities::accept::Accept, generate_activity_id}; use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, @@ -36,7 +34,7 @@ impl Follow { #[async_trait::async_trait] impl ActivityHandler for Follow { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { diff --git a/src/federation/activities/mod.rs b/src/federation/activities/mod.rs index ea9ead4..ca86d97 100644 --- a/src/federation/activities/mod.rs +++ b/src/federation/activities/mod.rs @@ -1,10 +1,12 @@ -use crate::database::DatabaseHandle; +use crate::database::article::DbArticle; +use crate::database::edit::{DbEdit, DbEditForm}; +use crate::database::MyDataHandle; use crate::error::Error; use crate::federation::activities::update_local_article::UpdateLocalArticle; use crate::federation::activities::update_remote_article::UpdateRemoteArticle; -use crate::federation::objects::article::DbArticle; -use crate::federation::objects::edit::DbEdit; +use crate::federation::objects::instance::DbInstance; use activitypub_federation::config::Data; +use activitypub_federation::fetch::object_id::ObjectId; pub mod accept; pub mod create_article; @@ -14,29 +16,27 @@ pub mod update_local_article; pub mod update_remote_article; pub async fn submit_article_update( - data: &Data, + data: &Data, new_text: String, original_article: &DbArticle, ) -> Result<(), Error> { - let edit = DbEdit::new(original_article, &new_text)?; + let form = DbEditForm::new(original_article, &new_text)?; + let edit = DbEdit::create(&form, &data.db_connection)?; if original_article.local { let updated_article = { let mut lock = data.articles.lock().unwrap(); - let article = lock.get_mut(original_article.ap_id.inner()).unwrap(); + let article = lock.get_mut(&original_article.ap_id).unwrap(); article.text = new_text; article.latest_version = edit.version.clone(); - article.edits.push(edit.clone()); article.clone() }; UpdateLocalArticle::send(updated_article, vec![], data).await?; } else { - UpdateRemoteArticle::send( - edit, - original_article.instance.dereference(data).await?, - data, - ) - .await?; + let instance: DbInstance = ObjectId::from(original_article.instance_id.clone()) + .dereference(data) + .await?; + UpdateRemoteArticle::send(edit, instance, data).await?; } Ok(()) } diff --git a/src/federation/activities/reject.rs b/src/federation/activities/reject.rs index 136f03f..91a8887 100644 --- a/src/federation/activities/reject.rs +++ b/src/federation/activities/reject.rs @@ -1,4 +1,4 @@ -use crate::database::DatabaseHandle; +use crate::database::MyDataHandle; use crate::error::MyResult; use crate::federation::objects::edit::ApubEdit; use crate::federation::objects::instance::DbInstance; @@ -30,7 +30,7 @@ impl RejectEdit { pub async fn send( edit: ApubEdit, user_instance: DbInstance, - data: &Data, + data: &Data, ) -> MyResult<()> { let local_instance = data.local_instance(); let id = generate_activity_id(local_instance.ap_id.inner())?; @@ -50,7 +50,7 @@ impl RejectEdit { #[async_trait::async_trait] impl ActivityHandler for RejectEdit { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { diff --git a/src/federation/activities/update_local_article.rs b/src/federation/activities/update_local_article.rs index b5e5d70..5e3ee58 100644 --- a/src/federation/activities/update_local_article.rs +++ b/src/federation/activities/update_local_article.rs @@ -1,6 +1,6 @@ -use crate::database::DatabaseHandle; +use crate::database::{article::DbArticle, MyDataHandle}; use crate::error::MyResult; -use crate::federation::objects::article::{ApubArticle, DbArticle}; +use crate::federation::objects::article::ApubArticle; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_activity_id; @@ -32,7 +32,7 @@ impl UpdateLocalArticle { pub async fn send( article: DbArticle, extra_recipients: Vec, - data: &Data, + data: &Data, ) -> MyResult<()> { debug_assert!(article.local); let local_instance = data.local_instance(); @@ -55,7 +55,7 @@ impl UpdateLocalArticle { #[async_trait::async_trait] impl ActivityHandler for UpdateLocalArticle { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { diff --git a/src/federation/activities/update_remote_article.rs b/src/federation/activities/update_remote_article.rs index 3a37d3a..0481501 100644 --- a/src/federation/activities/update_remote_article.rs +++ b/src/federation/activities/update_remote_article.rs @@ -1,7 +1,7 @@ -use crate::database::DatabaseHandle; +use crate::database::MyDataHandle; use crate::error::MyResult; -use crate::federation::objects::edit::{ApubEdit, DbEdit}; +use crate::federation::objects::edit::ApubEdit; use crate::federation::objects::instance::DbInstance; use crate::utils::generate_activity_id; use activitypub_federation::kinds::activity::UpdateType; @@ -13,6 +13,8 @@ use activitypub_federation::{ }; use diffy::{apply, Patch}; +use crate::database::article::DbArticle; +use crate::database::edit::DbEdit; use crate::federation::activities::reject::RejectEdit; use crate::federation::activities::update_local_article::UpdateLocalArticle; use serde::{Deserialize, Serialize}; @@ -35,7 +37,7 @@ impl UpdateRemoteArticle { pub async fn send( edit: DbEdit, article_instance: DbInstance, - data: &Data, + data: &Data, ) -> MyResult<()> { let local_instance = data.local_instance(); let id = generate_activity_id(local_instance.ap_id.inner())?; @@ -55,7 +57,7 @@ impl UpdateRemoteArticle { #[async_trait::async_trait] impl ActivityHandler for UpdateRemoteArticle { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Error = crate::error::Error; fn id(&self) -> &Url { @@ -80,13 +82,9 @@ impl ActivityHandler for UpdateRemoteArticle { match apply(&article_text, &patch) { Ok(applied) => { - let article = { - let edit = DbEdit::from_json(self.object.clone(), data).await?; - let mut lock = data.articles.lock().unwrap(); - let article = lock.get_mut(edit.article_id.inner()).unwrap(); - article.text = applied; - article.clone() - }; + let edit = DbEdit::from_json(self.object.clone(), data).await?; + let article = + DbArticle::update_text(edit.article_id, &applied, &mut data.db_connection)?; UpdateLocalArticle::send(article, vec![self.actor.dereference(data).await?], data) .await?; } diff --git a/src/federation/mod.rs b/src/federation/mod.rs index a04b0a4..8ea37a7 100644 --- a/src/federation/mod.rs +++ b/src/federation/mod.rs @@ -1,5 +1,6 @@ -use crate::database::{Database, DatabaseHandle}; +use crate::database::{FakeDatabase, MyData, MyDataHandle}; use crate::error::Error; +use crate::establish_db_connection; use crate::federation::objects::instance::DbInstance; use activitypub_federation::config::FederationConfig; use activitypub_federation::fetch::collection_id::CollectionId; @@ -13,13 +14,13 @@ pub mod activities; pub mod objects; pub mod routes; -pub async fn federation_config(hostname: &str) -> Result, Error> { - let ap_id = Url::parse(&format!("http://{}", hostname))?.into(); +pub async fn federation_config(hostname: &str) -> Result, Error> { + let ap_id = Url::parse(&format!("http://{}", hostname))?; 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 { - ap_id, + ap_id: ap_id.into(), articles_id, inbox, public_key: keypair.public_key, @@ -29,7 +30,7 @@ pub async fn federation_config(hostname: &str) -> Result Result, - pub instance: ObjectId, - /// List of all edits which make up this article, oldest first. - pub edits: Vec, - pub latest_version: EditVersion, - pub local: bool, -} - -impl DbArticle { - fn edits_id(&self) -> MyResult> { - Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?) - } -} - #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct ApubArticle { #[serde(rename = "type")] - kind: ArticleType, - id: ObjectId, + pub(crate) kind: ArticleType, + pub(crate) id: ObjectId, pub(crate) attributed_to: ObjectId, #[serde(deserialize_with = "deserialize_one_or_many")] pub(crate) to: Vec, @@ -50,7 +32,7 @@ pub struct ApubArticle { #[async_trait::async_trait] impl Object for DbArticle { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Kind = ApubArticle; type Error = Error; @@ -58,21 +40,18 @@ impl Object for DbArticle { object_id: Url, data: &Data, ) -> Result, Self::Error> { - let posts = data.articles.lock().unwrap(); - let res = posts - .clone() - .into_iter() - .find(|u| u.1.ap_id.inner() == &object_id) - .map(|u| u.1); - Ok(res) + let article = DbArticle::read_from_ap_id(&object_id.into(), &mut data.db_connection).ok(); + Ok(article) } async fn into_json(self, data: &Data) -> Result { - let instance = self.instance.dereference_local(data).await?; + let instance: DbInstance = ObjectId::from(self.instance_id) + .dereference_local(data) + .await?; Ok(ApubArticle { kind: Default::default(), - id: self.ap_id.clone(), - attributed_to: self.instance.clone(), + id: self.ap_id.clone().into(), + attributed_to: self.instance_id.clone().into(), to: vec![public(), instance.followers_url()?], edits: self.edits_id()?, latest_version: self.latest_version, @@ -91,26 +70,22 @@ impl Object for DbArticle { } async fn from_json(json: Self::Kind, data: &Data) -> Result { - let mut article = DbArticle { + let form = DbArticleForm { title: json.name, text: json.content, - ap_id: json.id, - instance: json.attributed_to, - // TODO: shouldnt overwrite existing edits - edits: vec![], - latest_version: json.latest_version, + ap_id: json.id.into(), + latest_version: json.latest_version.0, local: false, + instance_id: json.attributed_to.into(), }; + let mut article = DbArticle::create(&form, &data.db_connection)?; { let mut lock = data.articles.lock().unwrap(); - lock.insert(article.ap_id.inner().clone(), article.clone()); + lock.insert(article.ap_id.clone().into(), article.clone()); } - let edits = json.edits.dereference(&article, data).await?; - - // include edits in return value (they are already written to db, no need to do that here) - article.edits = edits.0; + json.edits.dereference(&article, data).await?; Ok(article) } diff --git a/src/federation/objects/articles_collection.rs b/src/federation/objects/articles_collection.rs index c40cf02..b86858a 100644 --- a/src/federation/objects/articles_collection.rs +++ b/src/federation/objects/articles_collection.rs @@ -1,6 +1,6 @@ -use crate::database::DatabaseHandle; +use crate::database::{article::DbArticle, MyDataHandle}; use crate::error::Error; -use crate::federation::objects::article::{ApubArticle, DbArticle}; +use crate::federation::objects::article::ApubArticle; use crate::federation::objects::instance::DbInstance; use activitypub_federation::kinds::collection::CollectionType; @@ -28,7 +28,7 @@ pub struct DbArticleCollection(Vec); #[async_trait::async_trait] impl Collection for DbArticleCollection { type Owner = DbInstance; - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Kind = ArticleCollection; type Error = Error; diff --git a/src/federation/objects/edit.rs b/src/federation/objects/edit.rs index 1e494d1..c26ee90 100644 --- a/src/federation/objects/edit.rs +++ b/src/federation/objects/edit.rs @@ -1,54 +1,13 @@ -use crate::database::DatabaseHandle; -use crate::error::{Error, MyResult}; - -use crate::federation::objects::article::DbArticle; +use crate::database::article::DbArticle; +use crate::database::edit::{DbEdit, DbEditForm, EditVersion}; +use crate::database::MyDataHandle; +use crate::error::Error; use activitypub_federation::config::Data; use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::traits::Object; -use diffy::create_patch; use serde::{Deserialize, Serialize}; -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, PartialEq)] -pub struct DbEdit { - pub id: ObjectId, - pub diff: String, - pub article_id: ObjectId, - pub version: EditVersion, - pub local: bool, -} - -impl DbEdit { - pub fn new(original_article: &DbArticle, updated_text: &str) -> MyResult { - 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(), - article_id: original_article.ap_id.clone(), - version: EditVersion(hash), - local: true, - }) - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub enum EditType { Edit, @@ -68,7 +27,7 @@ pub struct ApubEdit { #[async_trait::async_trait] impl Object for DbEdit { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Kind = ApubEdit; type Error = Error; @@ -80,18 +39,15 @@ impl Object for DbEdit { } async fn into_json(self, data: &Data) -> Result { - let article_version = { - let mut lock = data.articles.lock().unwrap(); - let article = lock.get_mut(self.article_id.inner()).unwrap(); - article.latest_version.clone() - }; + let article = DbArticle::read(self.article_id, &mut data.db_connection)?; Ok(ApubEdit { kind: EditType::Edit, - id: self.id, + id: self.ap_id.into(), content: self.diff, version: self.version, - previous_version: article_version, - object: self.article_id, + // TODO: this is wrong + previous_version: article.latest_version, + object: article.ap_id.into(), }) } @@ -104,16 +60,15 @@ impl Object for DbEdit { } async fn from_json(json: Self::Kind, data: &Data) -> Result { - let edit = Self { - id: json.id, + let article = json.object.dereference(data).await?; + let form = DbEditForm { + ap_id: json.id.into(), diff: json.content, - article_id: json.object, + article_id: article.id, 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 edit = DbEdit::create(&form, &mut data.db_connection)?; Ok(edit) } } diff --git a/src/federation/objects/edits_collection.rs b/src/federation/objects/edits_collection.rs index 1e029fe..f879f3d 100644 --- a/src/federation/objects/edits_collection.rs +++ b/src/federation/objects/edits_collection.rs @@ -1,8 +1,9 @@ -use crate::database::DatabaseHandle; +use crate::database::article::DbArticle; +use crate::database::MyDataHandle; use crate::error::Error; -use crate::federation::objects::article::DbArticle; -use crate::federation::objects::edit::{ApubEdit, DbEdit}; +use crate::federation::objects::edit::ApubEdit; +use crate::database::edit::DbEdit; use activitypub_federation::kinds::collection::OrderedCollectionType; use activitypub_federation::{ config::Data, @@ -28,7 +29,7 @@ pub struct DbEditCollection(pub Vec); #[async_trait::async_trait] impl Collection for DbEditCollection { type Owner = DbArticle; - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Kind = ApubEditCollection; type Error = Error; @@ -36,10 +37,7 @@ impl Collection for DbEditCollection { owner: &Self::Owner, data: &Data, ) -> Result { - let edits = { - let lock = data.articles.lock().unwrap(); - DbEditCollection(lock.get(owner.ap_id.inner()).unwrap().edits.clone()) - }; + let edits = DbEditCollection(DbEdit::for_article(owner.id, &mut data.db_connection)?); let edits = future::try_join_all( edits diff --git a/src/federation/objects/instance.rs b/src/federation/objects/instance.rs index 9a7c3c3..16b5a65 100644 --- a/src/federation/objects/instance.rs +++ b/src/federation/objects/instance.rs @@ -1,6 +1,6 @@ use crate::error::{Error, MyResult}; use crate::federation::objects::articles_collection::DbArticleCollection; -use crate::{database::DatabaseHandle, federation::activities::follow::Follow}; +use crate::{database::MyDataHandle, federation::activities::follow::Follow}; use activitypub_federation::activity_sending::SendActivityTask; use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::kinds::actor::ServiceType; @@ -55,11 +55,7 @@ impl DbInstance { .collect() } - pub async fn follow( - &self, - other: &DbInstance, - data: &Data, - ) -> Result<(), Error> { + pub async fn follow(&self, other: &DbInstance, data: &Data) -> Result<(), Error> { let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone())?; self.send(follow, vec![other.shared_inbox_or_inbox()], data) .await?; @@ -70,7 +66,7 @@ impl DbInstance { &self, activity: Activity, extra_recipients: Vec, - data: &Data, + data: &Data, ) -> Result<(), ::Error> where Activity: ActivityHandler + Serialize + Debug + Send + Sync, @@ -91,7 +87,7 @@ impl DbInstance { &self, activity: Activity, recipients: Vec, - data: &Data, + data: &Data, ) -> Result<(), ::Error> where Activity: ActivityHandler + Serialize + Debug + Send + Sync, @@ -111,7 +107,7 @@ impl DbInstance { #[async_trait::async_trait] impl Object for DbInstance { - type DataType = DatabaseHandle; + type DataType = MyDataHandle; type Kind = ApubInstance; type Error = Error; diff --git a/src/federation/routes.rs b/src/federation/routes.rs index ac0632c..436a3aa 100644 --- a/src/federation/routes.rs +++ b/src/federation/routes.rs @@ -1,4 +1,4 @@ -use crate::database::DatabaseHandle; +use crate::database::MyDataHandle; use crate::error::MyResult; use crate::federation::activities::accept::Accept; use crate::federation::activities::follow::Follow; @@ -37,7 +37,7 @@ pub fn federation_routes() -> Router { #[debug_handler] async fn http_get_instance( - data: Data, + data: Data, ) -> MyResult>> { let db_instance = data.local_instance(); let json_instance = db_instance.into_json(&data).await?; @@ -46,7 +46,7 @@ async fn http_get_instance( #[debug_handler] async fn http_get_all_articles( - data: Data, + data: Data, ) -> MyResult>> { let collection = DbArticleCollection::read_local(&data.local_instance(), &data).await?; Ok(FederationJson(WithContext::new_default(collection))) @@ -55,7 +55,7 @@ async fn http_get_all_articles( #[debug_handler] async fn http_get_article( Path(title): Path, - data: Data, + data: Data, ) -> MyResult>> { let article = { let lock = data.articles.lock().unwrap(); @@ -68,7 +68,7 @@ async fn http_get_article( #[debug_handler] async fn http_get_article_edits( Path(title): Path, - data: Data, + data: Data, ) -> MyResult>> { let article = { let lock = data.articles.lock().unwrap(); @@ -93,12 +93,9 @@ pub enum InboxActivities { #[debug_handler] pub async fn http_post_inbox( - data: Data, + data: Data, activity_data: ActivityData, ) -> impl IntoResponse { - receive_activity::, DbInstance, DatabaseHandle>( - activity_data, - &data, - ) - .await + receive_activity::, DbInstance, MyDataHandle>(activity_data, &data) + .await } diff --git a/src/lib.rs b/src/lib.rs index 707e1b0..075c6fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ -use crate::utils::generate_activity_id; - -use activitypub_federation::config::FederationMiddleware; -use axum::{Router, Server}; - use crate::api::api_routes; use crate::error::MyResult; use crate::federation::routes::federation_routes; +use crate::utils::generate_activity_id; +use activitypub_federation::config::FederationMiddleware; +use axum::{Router, Server}; +use diesel::Connection; +use diesel::PgConnection; use federation::federation_config; use std::net::ToSocketAddrs; use tracing::info; @@ -36,3 +36,9 @@ pub async fn start(hostname: &str) -> MyResult<()> { Ok(()) } + +pub fn establish_db_connection() -> MyResult { + // TODO: read from config file + let database_url = "postgres://fediwiki:password@localhost:5432/fediwiki"; + Ok(PgConnection::establish(&database_url)?) +} diff --git a/src/utils.rs b/src/utils.rs index dd9e87d..6883a7d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ +use crate::database::edit::DbEdit; +use crate::database::edit::EditVersion; use crate::error::MyResult; -use crate::federation::objects::edit::{DbEdit, EditVersion}; use anyhow::anyhow; use diffy::{apply, Patch}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; diff --git a/tests/common.rs b/tests/common.rs index f30c9f9..dfb241e 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -2,8 +2,8 @@ use activitypub_federation::fetch::object_id::ObjectId; use fediwiki::api::{ ApiConflict, CreateArticleData, EditArticleData, FollowInstance, GetArticleData, ResolveObject, }; +use fediwiki::database::DbArticle; use fediwiki::error::MyResult; -use fediwiki::federation::objects::article::DbArticle; use fediwiki::federation::objects::instance::DbInstance; use fediwiki::start; use once_cell::sync::Lazy; diff --git a/tests/test.rs b/tests/test.rs index ed6b16c..2a01662 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -10,8 +10,8 @@ use common::get; use fediwiki::api::{ ApiConflict, EditArticleData, ForkArticleData, ResolveObject, SearchArticleData, }; +use fediwiki::database::DbArticle; use fediwiki::error::MyResult; -use fediwiki::federation::objects::article::DbArticle; use fediwiki::federation::objects::edit::ApubEdit; use fediwiki::federation::objects::instance::DbInstance; use serial_test::serial; @@ -438,7 +438,7 @@ async fn test_fork_article() -> MyResult<()> { assert!(fork_res.local); let beta_instance: DbInstance = get(data.hostname_beta, "instance").await?; - assert_eq!(fork_res.instance, beta_instance.ap_id); + assert_eq!(fork_res.instance_id, beta_instance.ap_id); // now search returns two articles for this title (original and forked) let search_form = SearchArticleData {