diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index e255e36c2..f65389187 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -18,6 +18,7 @@ use lemmy_db_schema::{ community_block::CommunityBlock, email_verification::{EmailVerification, EmailVerificationForm}, images::{ImageDetails, RemoteImage}, + inbox::Inbox, instance::Instance, instance_block::InstanceBlock, local_site::LocalSite, @@ -973,6 +974,12 @@ pub fn generate_followers_url(actor_id: &DbUrl) -> Result { Ok(Url::parse(&format!("{actor_id}/followers"))?.into()) } +pub async fn generate_inbox(context: &LemmyContext) -> Result { + let url = format!("{}/inbox", context.settings().get_protocol_and_hostname()); + let parsed = Url::parse(&url)?.into(); + Ok(Inbox::read_or_create(&mut context.pool(), &parsed).await?) +} + pub fn generate_inbox_url(actor_id: &DbUrl) -> Result { Ok(Url::parse(&format!("{actor_id}/inbox"))?.into()) } diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index bf17d6f8e..41185617b 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -9,9 +9,8 @@ use lemmy_api_common::{ check_email_verified, check_registration_application, check_user_valid, - generate_inbox_url, + generate_inbox, generate_local_apub_endpoint, - generate_shared_inbox_url, honeypot_check, local_site_to_slur_regex, password_length_check, @@ -418,8 +417,7 @@ async fn create_person( // Register the new person let person_form = PersonInsertForm { actor_id: Some(actor_id.clone()), - inbox_url: Some(generate_inbox_url(&actor_id)?), - shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?), + inbox_id: Some(generate_inbox(&context).await?.id), private_key: Some(actor_keypair.private_key), ..PersonInsertForm::new(username.clone(), actor_keypair.public_key, instance_id) }; diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 406a77d94..9a74939f8 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -6,10 +6,7 @@ use crate::{ local_site_data_cached, objects::{instance::fetch_instance_actor_for_object, read_from_string_or_source_opt}, protocol::{ - objects::{ - person::{Person, UserTypes}, - Endpoints, - }, + objects::person::{Person, UserTypes}, ImageObject, Source, }, @@ -34,6 +31,7 @@ use lemmy_db_schema::{ sensitive::SensitiveString, source::{ activity::ActorType, + inbox::Inbox, local_site::LocalSite, person::{Person as DbPerson, PersonInsertForm, PersonUpdateForm}, }, @@ -99,7 +97,7 @@ impl Object for ApubPerson { } #[tracing::instrument(skip_all)] - async fn into_json(self, _context: &Data) -> LemmyResult { + async fn into_json(self, context: &Data) -> LemmyResult { let kind = if self.bot_account { UserTypes::Service } else { @@ -118,12 +116,13 @@ impl Object for ApubPerson { matrix_user_id: self.matrix_user_id.clone(), published: Some(self.published), outbox: generate_outbox_url(&self.actor_id)?.into(), - endpoints: self.shared_inbox_url.clone().map(|s| Endpoints { - shared_inbox: s.into(), - }), + endpoints: None, public_key: self.public_key(), updated: self.updated, - inbox: self.inbox_url.clone().into(), + inbox: Inbox::read(&mut context.pool(), self.inbox_id) + .await? + .url + .into(), }; Ok(person) } @@ -160,6 +159,7 @@ impl Object for ApubPerson { let bio = markdown_rewrite_remote_links_opt(bio, context).await; let avatar = proxy_image_link_opt_apub(person.icon.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(person.image.map(|i| i.url), context).await?; + let inbox = Inbox::read_or_create(&mut context.pool(), &person.inbox.into()).await?; // Some Mastodon users have `name: ""` (empty string), need to convert that to `None` // https://github.com/mastodon/mastodon/issues/25233 @@ -182,8 +182,7 @@ impl Object for ApubPerson { private_key: None, public_key: person.public_key.public_key_pem, last_refreshed_at: Some(naive_now()), - inbox_url: Some(person.inbox.into()), - shared_inbox_url: person.endpoints.map(|e| e.shared_inbox.into()), + inbox_id: Some(inbox.id), matrix_user_id: person.matrix_user_id, instance_id, }; @@ -209,10 +208,6 @@ impl Actor for ApubPerson { fn inbox(&self) -> Url { self.inbox_url.clone().into() } - - fn shared_inbox(&self) -> Option { - self.shared_inbox_url.clone().map(Into::into) - } } impl GetActorType for ApubPerson { diff --git a/crates/db_schema/src/impls/inbox.rs b/crates/db_schema/src/impls/inbox.rs new file mode 100644 index 000000000..028ce6164 --- /dev/null +++ b/crates/db_schema/src/impls/inbox.rs @@ -0,0 +1,25 @@ +use crate::{ + newtypes::{DbUrl, InboxId}, + schema::inbox, + source::inbox::Inbox, + utils::{get_conn, DbPool}, +}; +use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; +use diesel_async::RunQueryDsl; + +impl Inbox { + pub async fn read_or_create(pool: &mut DbPool<'_>, inbox: &DbUrl) -> Result { + let conn = &mut get_conn(pool).await?; + + insert_into(inbox::table) + .values(inbox::url.eq(inbox)) + .on_conflict_do_nothing() + .get_result::(conn) + .await + } + + pub async fn read(pool: &mut DbPool<'_>, id: InboxId) -> Result { + let conn = &mut get_conn(pool).await?; + inbox::table.find(id).first(conn).await + } +} diff --git a/crates/db_schema/src/impls/mod.rs b/crates/db_schema/src/impls/mod.rs index f115a101f..28c4d4aaf 100644 --- a/crates/db_schema/src/impls/mod.rs +++ b/crates/db_schema/src/impls/mod.rs @@ -12,6 +12,7 @@ pub mod federation_allowlist; pub mod federation_blocklist; pub mod federation_queue_state; pub mod images; +pub mod inbox; pub mod instance; pub mod instance_block; pub mod language; diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index fe1febef5..ff5357fdd 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -166,6 +166,11 @@ pub struct RegistrationApplicationId(i32); /// The oauth provider id. pub struct OAuthProviderId(pub i32); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "full", derive(DieselNewType, TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct InboxId(pub i32); + #[cfg(feature = "full")] #[derive(Serialize, Deserialize)] #[serde(remote = "Ltree")] diff --git a/crates/db_schema/src/source/inbox.rs b/crates/db_schema/src/source/inbox.rs new file mode 100644 index 000000000..1ee79db3a --- /dev/null +++ b/crates/db_schema/src/source/inbox.rs @@ -0,0 +1,18 @@ +use crate::newtypes::{DbUrl, InboxId}; +#[cfg(feature = "full")] +use crate::schema::inbox; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[skip_serializing_none] +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] +#[cfg_attr(feature = "full", diesel(table_name = inbox))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +pub struct Inbox { + pub id: InboxId, + pub url: DbUrl, +} diff --git a/crates/db_schema/src/source/mod.rs b/crates/db_schema/src/source/mod.rs index 377c1aaef..e150f96f9 100644 --- a/crates/db_schema/src/source/mod.rs +++ b/crates/db_schema/src/source/mod.rs @@ -17,6 +17,7 @@ pub mod federation_allowlist; pub mod federation_blocklist; pub mod federation_queue_state; pub mod images; +pub mod inbox; pub mod instance; pub mod instance_block; pub mod language; diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index b8b707291..53aaa6b6d 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -1,7 +1,7 @@ #[cfg(feature = "full")] use crate::schema::{person, person_follower}; use crate::{ - newtypes::{DbUrl, InstanceId, PersonId}, + newtypes::{DbUrl, InboxId, InstanceId, PersonId}, sensitive::SensitiveString, }; use chrono::{DateTime, Utc}; @@ -53,7 +53,7 @@ pub struct Person { pub instance_id: InstanceId, #[cfg_attr(feature = "full", ts(skip))] #[serde(skip, default)] - pub inbox_id: i32, + pub inbox_id: InboxId, } #[derive(Clone, derive_new::new)] @@ -88,7 +88,7 @@ pub struct PersonInsertForm { #[new(default)] pub deleted: Option, #[new(default)] - pub inbox_id: Option, + pub inbox_id: Option, #[new(default)] pub matrix_user_id: Option, #[new(default)] diff --git a/migrations/2024-09-24-110638_only-shared-inbox/up.sql b/migrations/2024-09-24-110638_only-shared-inbox/up.sql index bc06eea3d..b1d9909b6 100644 --- a/migrations/2024-09-24-110638_only-shared-inbox/up.sql +++ b/migrations/2024-09-24-110638_only-shared-inbox/up.sql @@ -4,7 +4,7 @@ -- TODO: add trigger which removes unused inbox items CREATE TABLE inbox ( id serial PRIMARY KEY, - url varchar(255) NOT NULL + url varchar(255) NOT NULL UNIQUE ); -- Move existing inbox values to inbox table, and replace with foreign key