diff --git a/upub/core/src/context.rs b/upub/core/src/context.rs index 4121e17..f589e0c 100644 --- a/upub/core/src/context.rs +++ b/upub/core/src/context.rs @@ -1,6 +1,6 @@ use std::{collections::BTreeSet, sync::Arc}; -use sea_orm::{DatabaseConnection, DbErr}; +use sea_orm::{DatabaseConnection, DbErr, QuerySelect, SelectColumns}; use crate::{config::Config, model}; use uriproxy::UriClass; @@ -52,8 +52,19 @@ impl Context { // TODO maybe we could provide a more descriptive error... let pkey = actor.private_key.as_deref().ok_or_else(|| DbErr::RecordNotFound("application private key".into()))?.to_string(); - let relay_sinks = model::relation::Entity::followers(&actor.id, &db).await?.ok_or_else(|| DbErr::RecordNotFound(actor.id.clone()))?; - let relay_sources = model::relation::Entity::following(&actor.id, &db).await?.ok_or_else(|| DbErr::RecordNotFound(actor.id.clone()))?; + let relay_sinks = crate::Query::related(None, Some(actor.internal), false) + .select_only() + .select_column(crate::model::actor::Column::Id) + .into_tuple::() + .all(&db) + .await?; + + let relay_sources = crate::Query::related(Some(actor.internal), None, false) + .select_only() + .select_column(crate::model::actor::Column::Id) + .into_tuple::() + .all(&db) + .await?; let relay = Relays { sources: BTreeSet::from_iter(relay_sources), diff --git a/upub/core/src/model/relation.rs b/upub/core/src/model/relation.rs index a2e7d53..5c01bff 100644 --- a/upub/core/src/model/relation.rs +++ b/upub/core/src/model/relation.rs @@ -1,4 +1,4 @@ -use sea_orm::{entity::prelude::*, QuerySelect, SelectColumns}; +use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "relations")] @@ -84,66 +84,3 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} - -impl Entity { - // TODO this is 2 queries!!! can it be optimized down to 1? - pub async fn followers(uid: &str, db: &impl ConnectionTrait) -> Result>, DbErr> { - let Some(internal_id) = super::actor::Entity::ap_to_internal(uid, db).await? - else { - return Ok(None); - }; - let out = Entity::find() - .join( - sea_orm::JoinType::InnerJoin, - Entity::belongs_to(super::actor::Entity) - .from(Column::Follower) - .to(super::actor::Column::Internal) - .into() - ) - .filter(Column::Accept.is_not_null()) - .filter(Column::Following.eq(internal_id)) - .select_only() - .select_column(super::actor::Column::Id) - .into_tuple::() - .all(db) - .await?; - - Ok(Some(out)) - } - - // TODO this is 2 queries!!! can it be optimized down to 1? - pub async fn following(uid: &str, db: &impl ConnectionTrait) -> Result>, DbErr> { - let Some(internal_id) = super::actor::Entity::ap_to_internal(uid, db).await? - else { - return Ok(None); - }; - let out = Entity::find() - .join( - sea_orm::JoinType::InnerJoin, - Entity::belongs_to(super::actor::Entity) - .from(Column::Following) - .to(super::actor::Column::Internal) - .into() - ) - .filter(Column::Accept.is_not_null()) - .filter(Column::Follower.eq(internal_id)) - .select_only() - .select_column(super::actor::Column::Id) - .into_tuple::() - .all(db) - .await?; - - Ok(Some(out)) - } - - // TODO this is 3 queries!!! can it be optimized down to 1? - pub fn is_following(follower: i64, following: i64) -> sea_orm::Selector> { - Entity::find() - .filter(Column::Accept.is_not_null()) - .filter(Column::Follower.eq(follower)) - .filter(Column::Following.eq(following)) - .select_only() - .select_column(Column::Internal) - .into_tuple::() - } -} diff --git a/upub/core/src/selector/query.rs b/upub/core/src/selector/query.rs index 8d6bb04..feb1254 100644 --- a/upub/core/src/selector/query.rs +++ b/upub/core/src/selector/query.rs @@ -70,4 +70,37 @@ impl Query { select } + + pub fn related(from: Option, to: Option, pending: bool) -> Select { + let mut condition = Condition::all(); + + if let Some(from) = from { + condition = condition.add(model::relation::Column::Follower.eq(from)); + } + + if let Some(to) = to { + condition = condition.add(model::relation::Column::Following.eq(to)); + } + + if !pending { + condition = condition.add(model::relation::Column::Accept.is_not_null()); + } + + let mut select = model::relation::Entity::find() + .join( + sea_orm::JoinType::InnerJoin, + model::relation::Entity::belongs_to(model::actor::Entity) + .from(model::relation::Column::Follower) + .to(model::actor::Column::Internal) + .into() + ) + .filter(condition) + .select_only(); + + for column in model::actor::Column::iter() { + select = select.select_column_as(column, format!("{}{}", model::actor::Entity.table_name(), column.to_string())); + } + + select + } } diff --git a/upub/core/src/traits/address.rs b/upub/core/src/traits/address.rs index fca62f1..fa70d21 100644 --- a/upub/core/src/traits/address.rs +++ b/upub/core/src/traits/address.rs @@ -1,5 +1,5 @@ use apb::target::Addressed; -use sea_orm::{ActiveValue::{NotSet, Set}, ConnectionTrait, DbErr, EntityTrait}; +use sea_orm::{ActiveValue::{NotSet, Set}, ConnectionTrait, DbErr, EntityTrait, QuerySelect, SelectColumns}; use crate::traits::fetch::Fetcher; @@ -126,9 +126,15 @@ async fn expand_addressing(targets: Vec, tx: &impl ConnectionTrait) -> R // only used for groups anyway?? if target.ends_with("/followers") { let target_id = target.replace("/followers", ""); - let mut followers = crate::model::relation::Entity::followers(&target_id, tx) + let target_internal = crate::model::actor::Entity::ap_to_internal(&target_id, tx) .await? - .unwrap_or_else(Vec::new); + .ok_or_else(|| DbErr::RecordNotFound(target_id.clone()))?; + let mut followers = crate::Query::related(None, Some(target_internal), false) + .select_only() + .select_column(crate::model::actor::Column::Id) + .into_tuple::() + .all(tx) + .await?; if followers.is_empty() { // stuff with zero addressing will never be seen again!!! TODO followers.push(target_id); } diff --git a/upub/routes/src/activitypub/actor/mod.rs b/upub/routes/src/activitypub/actor/mod.rs index 5d4e5d7..4886772 100644 --- a/upub/routes/src/activitypub/actor/mod.rs +++ b/upub/routes/src/activitypub/actor/mod.rs @@ -43,8 +43,8 @@ pub async fn view( // TODO these two queries are fast because of indexes but still are 2 subqueries for each // user GET, not even parallelized... should maybe add these as joins on the main query? so // that it's one roundtrip only - let followed_by_me = model::relation::Entity::is_following(my_id, internal_uid).any(ctx.db()).await?; - let following_me = model::relation::Entity::is_following(internal_uid, my_id).any(ctx.db()).await?; + let followed_by_me = upub::Query::related(Some(my_id), Some(internal_uid), false).any(ctx.db()).await?; + let following_me = upub::Query::related(Some(internal_uid), Some(my_id), false).any(ctx.db()).await?; (Some(followed_by_me), Some(following_me)) }, };