diff --git a/migrations/2024-11-11-142910_conflict-constraint/down.sql b/migrations/2024-11-11-142910_conflict-constraint/down.sql index a37e1a7..9e13a3e 100644 --- a/migrations/2024-11-11-142910_conflict-constraint/down.sql +++ b/migrations/2024-11-11-142910_conflict-constraint/down.sql @@ -1,2 +1 @@ -ALTER TABLE conflict DROP CONSTRAINT conflict_creator_id_fkey; -ALTER TABLE conflict ADD CONSTRAINT conflict_creator_id_fkey FOREIGN KEY (creator_id) REFERENCES local_user(id) ON UPDATE CASCADE ON DELETE CASCADE; \ No newline at end of file +select 1; \ No newline at end of file diff --git a/src/backend/database/article.rs b/src/backend/database/article.rs index dd2368d..994fb75 100644 --- a/src/backend/database/article.rs +++ b/src/backend/database/article.rs @@ -7,7 +7,13 @@ use crate::{ error::MyResult, federation::objects::edits_collection::DbEditCollection, }, - common::{ArticleView, DbArticle, DbEdit, EditVersion}, + common::{ + newtypes::{ArticleId, InstanceId}, + ArticleView, + DbArticle, + DbEdit, + EditVersion, + }, }; use activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId}; use diesel::{ @@ -29,7 +35,7 @@ pub struct DbArticleForm { pub title: String, pub text: String, pub ap_id: ObjectId, - pub instance_id: i32, + pub instance_id: InstanceId, pub local: bool, pub protected: bool, } @@ -59,26 +65,26 @@ impl DbArticle { .get_result(conn.deref_mut())?) } - pub fn update_text(id: i32, text: &str, data: &IbisData) -> MyResult { + pub fn update_text(id: ArticleId, text: &str, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; Ok(diesel::update(article::dsl::article.find(id)) .set(article::dsl::text.eq(text)) .get_result::(conn.deref_mut())?) } - pub fn update_protected(id: i32, locked: bool, data: &IbisData) -> MyResult { + pub fn update_protected(id: ArticleId, locked: bool, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; Ok(diesel::update(article::dsl::article.find(id)) .set(article::dsl::protected.eq(locked)) .get_result::(conn.deref_mut())?) } - pub fn read(id: i32, data: &IbisData) -> MyResult { + pub fn read(id: ArticleId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; Ok(article::table.find(id).get_result(conn.deref_mut())?) } - pub fn read_view(id: i32, data: &IbisData) -> MyResult { + pub fn read_view(id: ArticleId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; let article: DbArticle = { article::table.find(id).get_result(conn.deref_mut())? }; let latest_version = article.latest_edit_version(data)?; @@ -139,7 +145,7 @@ impl DbArticle { /// TODO: Should get rid of only_local param and rely on instance_id pub fn read_all( only_local: Option, - instance_id: Option, + instance_id: Option, data: &IbisData, ) -> MyResult> { let mut conn = data.db_pool.get()?; diff --git a/src/backend/database/conflict.rs b/src/backend/database/conflict.rs index 890db7a..0f95793 100644 --- a/src/backend/database/conflict.rs +++ b/src/backend/database/conflict.rs @@ -5,9 +5,15 @@ use crate::{ federation::activities::submit_article_update, utils::generate_article_version, }, - common::{ApiConflict, DbArticle, DbEdit, DbPerson, EditVersion}, + common::{ + newtypes::{ArticleId, ConflictId, PersonId}, + ApiConflict, + DbArticle, + DbEdit, + DbPerson, + EditVersion, + }, }; -use crate::common::newtypes::PersonId; use activitypub_federation::config::Data; use diesel::{ delete, @@ -29,12 +35,12 @@ use std::ops::DerefMut; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Queryable, Selectable, Identifiable)] #[diesel(table_name = conflict, check_for_backend(diesel::pg::Pg), belongs_to(DbArticle, foreign_key = article_id))] pub struct DbConflict { - pub id: i32, + pub id: ConflictId, pub hash: EditVersion, pub diff: String, pub summary: String, pub creator_id: PersonId, - pub article_id: i32, + pub article_id: ArticleId, pub previous_version_id: EditVersion, } @@ -45,7 +51,7 @@ pub struct DbConflictForm { pub diff: String, pub summary: String, pub creator_id: PersonId, - pub article_id: i32, + pub article_id: ArticleId, pub previous_version_id: EditVersion, } @@ -65,7 +71,7 @@ impl DbConflict { } /// Delete a merge conflict after it is resolved. - pub fn delete(id: i32, data: &IbisData) -> MyResult { + pub fn delete(id: ConflictId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?) } diff --git a/src/backend/database/edit.rs b/src/backend/database/edit.rs index 3e47319..30a3b90 100644 --- a/src/backend/database/edit.rs +++ b/src/backend/database/edit.rs @@ -4,14 +4,19 @@ use crate::{ error::MyResult, IbisData, }, - common::{DbArticle, DbEdit, EditVersion, EditView}, + common::{ + newtypes::{ArticleId, PersonId}, + DbArticle, + DbEdit, + EditVersion, + EditView, + }, }; use activitypub_federation::fetch::object_id::ObjectId; use chrono::{DateTime, Utc}; use diesel::{insert_into, AsChangeset, ExpressionMethods, Insertable, QueryDsl, RunQueryDsl}; use diffy::create_patch; use std::ops::DerefMut; -use crate::common::newtypes::PersonId; #[derive(Debug, Clone, Insertable, AsChangeset)] #[diesel(table_name = edit, check_for_backend(diesel::pg::Pg))] @@ -21,7 +26,7 @@ pub struct DbEditForm { pub ap_id: ObjectId, pub diff: String, pub summary: String, - pub article_id: i32, + pub article_id: ArticleId, pub previous_version_id: EditVersion, pub created: DateTime, } diff --git a/src/backend/database/instance.rs b/src/backend/database/instance.rs index 1242888..44f7cf9 100644 --- a/src/backend/database/instance.rs +++ b/src/backend/database/instance.rs @@ -10,7 +10,7 @@ use crate::{ instance_collection::DbInstanceCollection, }, }, - common::{DbInstance, DbPerson, InstanceView}, + common::{newtypes::InstanceId, DbInstance, DbPerson, InstanceView}, }; use activitypub_federation::{ config::Data, @@ -54,7 +54,7 @@ impl DbInstance { .get_result(conn.deref_mut())?) } - pub fn read(id: i32, data: &IbisData) -> MyResult { + pub fn read(id: InstanceId, data: &IbisData) -> MyResult { let mut conn = data.db_pool.get()?; Ok(instance::table.find(id).get_result(conn.deref_mut())?) } @@ -76,7 +76,7 @@ impl DbInstance { .get_result(conn.deref_mut())?) } - pub fn read_view(id: Option, data: &Data) -> MyResult { + pub fn read_view(id: Option, data: &Data) -> MyResult { let instance = match id { Some(id) => DbInstance::read(id, data), None => DbInstance::read_local_instance(data), @@ -113,7 +113,7 @@ impl DbInstance { Ok(()) } - pub fn read_followers(id_: i32, data: &IbisData) -> MyResult> { + pub fn read_followers(id_: InstanceId, data: &IbisData) -> MyResult> { use crate::backend::database::schema::person; use instance_follow::dsl::{follower_id, instance_id}; let mut conn = data.db_pool.get()?; diff --git a/src/backend/database/user.rs b/src/backend/database/user.rs index 6328329..b41cb00 100644 --- a/src/backend/database/user.rs +++ b/src/backend/database/user.rs @@ -7,6 +7,7 @@ use crate::{ error::MyResult, }, common::{ + newtypes::PersonId, utils::http_protocol_str, DbInstance, DbLocalUser, @@ -14,7 +15,6 @@ use crate::{ LocalUserView, }, }; -use crate::common::newtypes::PersonId; use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, diff --git a/src/backend/federation/activities/mod.rs b/src/backend/federation/activities/mod.rs index ece2f96..5ea0c08 100644 --- a/src/backend/federation/activities/mod.rs +++ b/src/backend/federation/activities/mod.rs @@ -7,11 +7,16 @@ use crate::{ update_remote_article::UpdateRemoteArticle, }, }, - common::{DbArticle, DbEdit, DbInstance, EditVersion}, + common::{ + newtypes::{EditId, PersonId}, + DbArticle, + DbEdit, + DbInstance, + EditVersion, + }, }; use activitypub_federation::config::Data; use chrono::Utc; -use crate::common::newtypes::PersonId; pub mod accept; pub mod create_article; @@ -43,7 +48,7 @@ pub async fn submit_article_update( } else { // dont insert edit into db, might be invalid in case of conflict let edit = DbEdit { - id: -1, + id: EditId(-1), creator_id, hash: form.hash, ap_id: form.ap_id, diff --git a/src/backend/utils.rs b/src/backend/utils.rs index dbd489d..3f76918 100644 --- a/src/backend/utils.rs +++ b/src/backend/utils.rs @@ -46,7 +46,11 @@ pub fn generate_article_version(edits: &Vec, version: &EditVersion) -> #[cfg(test)] mod test { use super::*; - use crate::common::{newtypes::PersonId, DbEdit, DbPerson}; + use crate::common::{ + newtypes::{ArticleId, EditId, PersonId}, + DbEdit, + DbPerson, + }; use activitypub_federation::fetch::object_id::ObjectId; use chrono::Utc; use diffy::create_patch; @@ -56,13 +60,13 @@ mod test { let diff = create_patch(a, b).to_string(); Ok(EditView { edit: DbEdit { - id: 0, + id: EditId(0), creator_id: PersonId(0), hash: EditVersion::new(&diff), ap_id: ObjectId::parse("http://example.com")?, diff, summary: String::new(), - article_id: 0, + article_id: ArticleId(0), previous_version_id: Default::default(), created: Utc::now(), }, diff --git a/src/common/mod.rs b/src/common/mod.rs index 0405af5..2f01e83 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,7 +3,7 @@ pub mod utils; pub mod validation; use chrono::{DateTime, Utc}; -use newtypes::PersonId; +use newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use url::Url; @@ -26,13 +26,13 @@ pub const MAIN_PAGE_NAME: &str = "Main_Page"; pub struct GetArticleForm { pub title: Option, pub domain: Option, - pub id: Option, + pub id: Option, } #[derive(Deserialize, Serialize, Clone)] pub struct ListArticlesForm { pub only_local: Option, - pub instance_id: Option, + pub instance_id: Option, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] @@ -48,14 +48,14 @@ pub struct ArticleView { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))] #[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg), belongs_to(DbInstance, foreign_key = instance_id)))] pub struct DbArticle { - pub id: i32, + pub id: ArticleId, pub title: String, pub text: String, #[cfg(feature = "ssr")] pub ap_id: ObjectId, #[cfg(not(feature = "ssr"))] pub ap_id: String, - pub instance_id: i32, + pub instance_id: InstanceId, pub local: bool, pub protected: bool, } @@ -67,7 +67,7 @@ pub struct DbArticle { pub struct DbEdit { // TODO: we could use hash as primary key, but that gives errors on forking because // the same edit is used for multiple articles - pub id: i32, + pub id: EditId, #[serde(skip)] pub creator_id: PersonId, /// UUID built from sha224 hash of diff @@ -78,7 +78,7 @@ pub struct DbEdit { pub ap_id: String, pub diff: String, pub summary: String, - pub article_id: i32, + pub article_id: ArticleId, /// First edit of an article always has `EditVersion::default()` here pub previous_version_id: EditVersion, pub created: DateTime, @@ -145,10 +145,10 @@ pub struct LocalUserView { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))] #[cfg_attr(feature = "ssr", diesel(table_name = local_user, check_for_backend(diesel::pg::Pg)))] pub struct DbLocalUser { - pub id: i32, + pub id: InstanceId, #[serde(skip)] pub password_encrypted: String, - pub person_id: i32, + pub person_id: PersonId, pub admin: bool, } @@ -189,7 +189,7 @@ pub struct CreateArticleForm { #[derive(Deserialize, Serialize, Debug)] pub struct EditArticleForm { /// Id of the article to edit - pub article_id: i32, + pub article_id: ArticleId, /// Full, new text of the article. A diff against `previous_version` is generated on the backend /// side to handle conflicts. pub new_text: String, @@ -199,29 +199,29 @@ pub struct EditArticleForm { /// [ApiConflict.previous_version] pub previous_version_id: EditVersion, /// If you are resolving a conflict, pass the id to delete conflict from the database - pub resolve_conflict_id: Option, + pub resolve_conflict_id: Option, } #[derive(Deserialize, Serialize, Debug)] pub struct ProtectArticleForm { - pub article_id: i32, + pub article_id: ArticleId, pub protected: bool, } #[derive(Deserialize, Serialize)] pub struct ForkArticleForm { - pub article_id: i32, + pub article_id: ArticleId, pub new_title: String, } #[derive(Deserialize, Serialize, Debug)] pub struct GetInstance { - pub id: Option, + pub id: Option, } #[derive(Deserialize, Serialize, Debug)] pub struct FollowInstance { - pub id: i32, + pub id: InstanceId, } #[derive(Deserialize, Serialize, Clone)] @@ -236,7 +236,7 @@ pub struct ResolveObject { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ApiConflict { - pub id: i32, + pub id: ConflictId, pub hash: EditVersion, pub three_way_merge: String, pub summary: String, @@ -248,7 +248,7 @@ pub struct ApiConflict { #[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))] #[cfg_attr(feature = "ssr", diesel(table_name = instance, check_for_backend(diesel::pg::Pg)))] pub struct DbInstance { - pub id: i32, + pub id: InstanceId, pub domain: String, #[cfg(feature = "ssr")] pub ap_id: ObjectId, diff --git a/src/common/newtypes.rs b/src/common/newtypes.rs new file mode 100644 index 0000000..f4187a0 --- /dev/null +++ b/src/common/newtypes.rs @@ -0,0 +1,23 @@ +#[cfg(feature = "ssr")] +use diesel_derive_newtype::DieselNewType; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(DieselNewType))] +pub struct PersonId(pub i32); + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(DieselNewType))] +pub struct ArticleId(pub i32); + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(DieselNewType))] +pub struct EditId(pub i32); + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(DieselNewType))] +pub struct InstanceId(pub i32); + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(DieselNewType))] +pub struct ConflictId(pub i32); diff --git a/src/frontend/components/instance_follow_button.rs b/src/frontend/components/instance_follow_button.rs index 6d56cea..504f107 100644 --- a/src/frontend/components/instance_follow_button.rs +++ b/src/frontend/components/instance_follow_button.rs @@ -1,5 +1,5 @@ use crate::{ - common::{DbInstance, FollowInstance}, + common::{newtypes::InstanceId, DbInstance, FollowInstance}, frontend::app::GlobalState, }; use leptos::{component, *}; @@ -7,7 +7,7 @@ use leptos::{component, *}; #[component] pub fn InstanceFollowButton(instance: DbInstance) -> impl IntoView { let global_state = use_context::>().unwrap(); - let follow_action = create_action(move |instance_id: &i32| { + let follow_action = create_action(move |instance_id: &InstanceId| { let instance_id = *instance_id; async move { let form = FollowInstance { id: instance_id }; diff --git a/src/frontend/pages/article/actions.rs b/src/frontend/pages/article/actions.rs index 53e1c9b..21c75fa 100644 --- a/src/frontend/pages/article/actions.rs +++ b/src/frontend/pages/article/actions.rs @@ -1,5 +1,5 @@ use crate::{ - common::{ForkArticleForm, ProtectArticleForm}, + common::{newtypes::ArticleId, ForkArticleForm, ProtectArticleForm}, frontend::{ app::GlobalState, article_link, @@ -18,7 +18,7 @@ pub fn ArticleActions() -> impl IntoView { let (new_title, set_new_title) = create_signal(String::new()); let (fork_response, set_fork_response) = create_signal(Option::::None); let (error, set_error) = create_signal(None::); - let fork_action = create_action(move |(article_id, new_title): &(i32, String)| { + let fork_action = create_action(move |(article_id, new_title): &(ArticleId, String)| { let params = ForkArticleForm { article_id: *article_id, new_title: new_title.to_string(), @@ -34,7 +34,7 @@ pub fn ArticleActions() -> impl IntoView { } } }); - let protect_action = create_action(move |(id, protected): &(i32, bool)| { + let protect_action = create_action(move |(id, protected): &(ArticleId, bool)| { let params = ProtectArticleForm { article_id: *id, protected: !protected, diff --git a/src/frontend/pages/article/edit.rs b/src/frontend/pages/article/edit.rs index aeb1847..3cd6bff 100644 --- a/src/frontend/pages/article/edit.rs +++ b/src/frontend/pages/article/edit.rs @@ -1,5 +1,5 @@ use crate::{ - common::{ApiConflict, ArticleView, EditArticleForm}, + common::{newtypes::ConflictId, ApiConflict, ArticleView, EditArticleForm}, frontend::{ app::GlobalState, components::{ @@ -32,7 +32,7 @@ pub fn EditArticle() -> impl IntoView { let conflict_id = move || use_params_map().get_untracked().get("conflict_id").cloned(); if let Some(conflict_id) = conflict_id() { create_action(move |conflict_id: &String| { - let conflict_id: i32 = conflict_id.parse().unwrap(); + let conflict_id = ConflictId(conflict_id.parse().unwrap()); async move { let conflict = GlobalState::api_client() .get_conflicts() diff --git a/src/frontend/pages/conflicts.rs b/src/frontend/pages/conflicts.rs index 97c785c..483fb29 100644 --- a/src/frontend/pages/conflicts.rs +++ b/src/frontend/pages/conflicts.rs @@ -21,7 +21,7 @@ pub fn Conflicts() -> impl IntoView { let link = format!( "{}/edit/{}", article_link(&c.article), - c.id, + c.id.0, ); view! {