From 3baa8ed839bca6dbb2419ff29d3bd9b475f5d39c Mon Sep 17 00:00:00 2001 From: alemi Date: Thu, 26 Dec 2024 17:15:10 +0100 Subject: [PATCH] feat: separate RichObject and RichActivity --- upub/core/src/selector/batch.rs | 54 +++++-- upub/core/src/selector/mod.rs | 6 +- upub/core/src/selector/query.rs | 5 - upub/core/src/selector/rich.rs | 133 ++++++++---------- upub/routes/src/activitypub/object/context.rs | 4 +- 5 files changed, 107 insertions(+), 95 deletions(-) diff --git a/upub/core/src/selector/batch.rs b/upub/core/src/selector/batch.rs index 096a800..230ef01 100644 --- a/upub/core/src/selector/batch.rs +++ b/upub/core/src/selector/batch.rs @@ -1,7 +1,7 @@ use std::collections::{hash_map::Entry, HashMap}; use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter}; -use super::RichActivity; +use super::{RichActivity, RichObject}; #[allow(async_fn_in_trait)] pub trait BatchFillable: Sized { @@ -9,17 +9,49 @@ pub trait BatchFillable: Sized { where E: BatchFillableComparison + EntityTrait, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait, - RichActivity: BatchFillableAcceptor>; + RichObject: BatchFillableAcceptor>; +} + +impl BatchFillable for RichActivity { + async fn with_batched(mut self, tx: &impl ConnectionTrait) -> Result + where + E: BatchFillableComparison + EntityTrait, + E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait, + RichObject: BatchFillableAcceptor>, + { + if let Some(obj) = self.object { + self.object = Some(obj.with_batched::(tx).await?); + } + + Ok(self) + } +} + +impl BatchFillable for Vec { + async fn with_batched(self, tx: &impl ConnectionTrait) -> Result + where + E: BatchFillableComparison + EntityTrait, + E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait, + RichObject: BatchFillableAcceptor> + { + // TODO can we do this in-place rather than copying everything to a new vec? + let mut out = Vec::new(); + for item in self { + out.push(item.with_batched::(tx).await?); + } + + Ok(out) + } } -impl BatchFillable for Vec { +impl BatchFillable for Vec { // TODO 3 iterations... can we make it in less passes? async fn with_batched(mut self, tx: &impl ConnectionTrait) -> Result where E: BatchFillableComparison + EntityTrait, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait, - RichActivity: BatchFillableAcceptor>, + RichObject: BatchFillableAcceptor>, { let ids : Vec = self.iter().filter_map(|x| Some(x.object.as_ref()?.internal)).collect(); let batch = E::find() @@ -46,12 +78,12 @@ impl BatchFillable for Vec { } } -impl BatchFillable for RichActivity { +impl BatchFillable for RichObject { async fn with_batched(mut self, tx: &impl ConnectionTrait) -> Result where E: BatchFillableComparison + EntityTrait, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait, - RichActivity: BatchFillableAcceptor>, + RichObject: BatchFillableAcceptor>, { if let Some(ref obj) = self.object { let batch =E::find() @@ -69,7 +101,7 @@ impl BatchFillable for RichActivity { mod hell { use sea_orm::{sea_query::IntoCondition, ColumnTrait, ConnectionTrait, DbErr, EntityTrait}; -use crate::selector::rich::{RichHashtag, RichMention}; + use crate::selector::rich::{RichHashtag, RichMention}; pub trait BatchFillableComparison { fn comparison(ids: Vec) -> sea_orm::Condition; @@ -115,26 +147,26 @@ use crate::selector::rich::{RichHashtag, RichMention}; } } -#[allow(async_fn_in_trait)] + #[allow(async_fn_in_trait)] pub trait BatchFillableAcceptor { async fn accept(&mut self, batch: B, tx: &impl ConnectionTrait) -> Result<(), DbErr>; } - impl BatchFillableAcceptor> for super::RichActivity { + impl BatchFillableAcceptor> for super::RichObject { async fn accept(&mut self, batch: Vec, _tx: &impl ConnectionTrait) -> Result<(), DbErr> { self.attachments = Some(batch); Ok(()) } } - impl BatchFillableAcceptor> for super::RichActivity { + impl BatchFillableAcceptor> for super::RichObject { async fn accept(&mut self, batch: Vec, _tx: &impl ConnectionTrait) -> Result<(), DbErr> { self.hashtags = Some(batch.into_iter().map(|x| RichHashtag { hash: x }).collect()); Ok(()) } } - impl BatchFillableAcceptor> for super::RichActivity { + impl BatchFillableAcceptor> for super::RichObject { async fn accept(&mut self, batch: Vec, tx: &impl ConnectionTrait) -> Result<(), DbErr> { // TODO batch load users from mentions rather than doing for loop let mut mentions = Vec::new(); diff --git a/upub/core/src/selector/mod.rs b/upub/core/src/selector/mod.rs index 75c8b9a..05c1fd8 100644 --- a/upub/core/src/selector/mod.rs +++ b/upub/core/src/selector/mod.rs @@ -5,8 +5,4 @@ mod query; pub use query::Query; mod rich; -pub use rich::{RichActivity, RichNotification}; - - - - +pub use rich::{RichActivity, RichObject, RichNotification}; diff --git a/upub/core/src/selector/query.rs b/upub/core/src/selector/query.rs index 4667773..8fdf63f 100644 --- a/upub/core/src/selector/query.rs +++ b/upub/core/src/selector/query.rs @@ -58,11 +58,6 @@ impl Query { select = select.select_column_as(col, format!("{}{}", model::object::Entity.table_name(), col.to_string())); } - select = select.select_column_as( - model::addressing::Column::Published, - format!("{}{}", model::addressing::Entity.table_name(), model::addressing::Column::Published.to_string()) - ); - if let Some(uid) = my_id { select = select .join( diff --git a/upub/core/src/selector/rich.rs b/upub/core/src/selector/rich.rs index 7661db4..dcca324 100644 --- a/upub/core/src/selector/rich.rs +++ b/upub/core/src/selector/rich.rs @@ -32,23 +32,70 @@ impl IntoActivityPub for RichHashtag { } } -pub struct RichActivity { - pub activity: Option, +pub struct RichObject { pub object: Option, pub liked: Option, pub attachments: Option>, pub hashtags: Option>, pub mentions: Option>, +} + +impl FromQueryResult for RichObject { + fn from_query_result(res: &QueryResult, _pre: &str) -> Result { + Ok(RichObject { + attachments: None, + hashtags: None, + mentions: None, + liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(), + object: crate::model::object::Model::from_query_result_optional(res, crate::model::object::Entity.table_name())?, + }) + } +} + +impl IntoActivityPub for RichObject { + fn into_activity_pub_json(self, ctx: &crate::Context) -> serde_json::Value { + use apb::ObjectMut; + match self.object { + Some(object) => { + let mut tags = Vec::new(); + if let Some(mentions) = self.mentions { + for mention in mentions { + tags.push(mention.into_activity_pub_json(ctx)); + } + } + if let Some(hashtags) = self.hashtags { + for hash in hashtags { + tags.push(hash.into_activity_pub_json(ctx)); + } + } + object.into_activity_pub_json(ctx) + .set_liked_by_me(if self.liked.is_some() { Some(true) } else { None }) + .set_tag(apb::Node::maybe_array(tags)) + .set_attachment(match self.attachments { + None => apb::Node::Empty, + Some(vec) => apb::Node::array( + vec.into_iter() + .map(|x| x.into_activity_pub_json(ctx)) + .collect() + ), + }) + }, + None => serde_json::Value::Null, + } + } +} + +pub struct RichActivity { + pub activity: Option, + pub object: Option, pub discovered: chrono::DateTime, } impl FromQueryResult for RichActivity { fn from_query_result(res: &QueryResult, _pre: &str) -> Result { Ok(RichActivity { - attachments: None, hashtags: None, mentions: None, - liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(), - object: crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name()).ok(), - activity: crate::model::activity::Model::from_query_result(res, crate::model::activity::Entity.table_name()).ok(), + object: RichObject::from_query_result_optional(res, _pre)?, + activity: crate::model::activity::Model::from_query_result_optional(res, crate::model::activity::Entity.table_name())?, discovered: res.try_get( crate::model::addressing::Entity.table_name(), &crate::model::addressing::Column::Published.to_string() @@ -63,76 +110,18 @@ impl IntoActivityPub for RichActivity { match (self.activity, self.object) { (None, None) => serde_json::Value::Null, - (Some(activity), None) => { - let obj = apb::Node::maybe_link(activity.object.clone()); - activity.into_activity_pub_json(ctx).set_object(obj) - }, + (Some(activity), None) => activity.into_activity_pub_json(ctx), - (maybe_activity, Some(object)) => { - let mut tags = Vec::new(); - if let Some(mentions) = self.mentions { - for mention in mentions { - tags.push(mention.into_activity_pub_json(ctx)); - } - } - if let Some(hashtags) = self.hashtags { - for hash in hashtags { - tags.push(hash.into_activity_pub_json(ctx)); - } - } + (None, Some(object)) => apb::new() + .set_activity_type(Some(apb::ActivityType::View)) + .set_published(Some(self.discovered)) + .set_object(apb::Node::object(object.into_activity_pub_json(ctx))), - let activity = match maybe_activity { - Some(activity) => activity.into_activity_pub_json(ctx), - None => apb::new() - .set_activity_type(Some(apb::ActivityType::View)) - .set_published(Some(self.discovered)) - }; - - activity - .set_object(apb::Node::object( - object.into_activity_pub_json(ctx) - .set_liked_by_me(if self.liked.is_some() { Some(true) } else { None }) - .set_tag(apb::Node::maybe_array(tags)) - .set_attachment(match self.attachments { - None => apb::Node::Empty, - Some(vec) => apb::Node::array( - vec.into_iter().map(|x| x.into_activity_pub_json(ctx)).collect() - ), - }) - )) - }, + (Some(activity), Some(object)) => activity + .into_activity_pub_json(ctx) + .set_object(apb::Node::object(object.into_activity_pub_json(ctx))), } } - - // // TODO ughhh cant make it a trait because there's this different one!!! - // pub fn object_ap(self) -> serde_json::Value { - // use apb::ObjectMut; - // match self.object { - // Some(object) => { - // let mut tags = Vec::new(); - // if let Some(mentions) = self.mentions { - // for mention in mentions { - // tags.push(mention.ap()); - // } - // } - // if let Some(hashtags) = self.hashtags { - // for hash in hashtags { - // tags.push(hash.ap()); - // } - // } - // object.ap() - // .set_liked_by_me(if self.liked.is_some() { Some(true) } else { None }) - // .set_tag(apb::Node::maybe_array(tags)) - // .set_attachment(match self.attachments { - // None => apb::Node::Empty, - // Some(vec) => apb::Node::array( - // vec.into_iter().map(|x| x.ap()).collect() - // ), - // }) - // }, - // None => serde_json::Value::Null, - // } - // } } pub struct RichNotification { diff --git a/upub/routes/src/activitypub/object/context.rs b/upub/routes/src/activitypub/object/context.rs index 8e15367..a3d81dc 100644 --- a/upub/routes/src/activitypub/object/context.rs +++ b/upub/routes/src/activitypub/object/context.rs @@ -1,6 +1,6 @@ use axum::extract::{Path, Query, State}; use sea_orm::{ColumnTrait, Order, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect}; -use upub::{model, selector::{BatchFillable, RichActivity}, Context}; +use upub::{model, selector::{BatchFillable, RichObject}, Context}; use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity}; @@ -36,7 +36,7 @@ pub async fn page( .order_by(model::object::Column::Published, Order::Asc) .limit(limit) .offset(offset) - .into_model::() + .into_model::() .all(ctx.db()) .await? .with_batched::(ctx.db())