feat: separate RichObject and RichActivity

This commit is contained in:
əlemi 2024-12-26 17:15:10 +01:00
parent 0c6be216b7
commit 3baa8ed839
Signed by: alemi
GPG key ID: A4895B84D311642C
5 changed files with 107 additions and 95 deletions

View file

@ -1,7 +1,7 @@
use std::collections::{hash_map::Entry, HashMap}; use std::collections::{hash_map::Entry, HashMap};
use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter}; use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter};
use super::RichActivity; use super::{RichActivity, RichObject};
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait BatchFillable: Sized { pub trait BatchFillable: Sized {
@ -9,17 +9,49 @@ pub trait BatchFillable: Sized {
where where
E: BatchFillableComparison + EntityTrait, E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>; RichObject: BatchFillableAcceptor<Vec<E::Model>>;
}
impl BatchFillable for RichActivity {
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>,
{
if let Some(obj) = self.object {
self.object = Some(obj.with_batched::<E>(tx).await?);
}
Ok(self)
}
}
impl BatchFillable for Vec<RichActivity> {
async fn with_batched<E>(self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{
// 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::<E>(tx).await?);
}
Ok(out)
}
} }
impl BatchFillable for Vec<RichActivity> { impl BatchFillable for Vec<RichObject> {
// TODO 3 iterations... can we make it in less passes? // TODO 3 iterations... can we make it in less passes?
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr> async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where where
E: BatchFillableComparison + EntityTrait, E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>, RichObject: BatchFillableAcceptor<Vec<E::Model>>,
{ {
let ids : Vec<i64> = self.iter().filter_map(|x| Some(x.object.as_ref()?.internal)).collect(); let ids : Vec<i64> = self.iter().filter_map(|x| Some(x.object.as_ref()?.internal)).collect();
let batch = E::find() let batch = E::find()
@ -46,12 +78,12 @@ impl BatchFillable for Vec<RichActivity> {
} }
} }
impl BatchFillable for RichActivity { impl BatchFillable for RichObject {
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr> async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where where
E: BatchFillableComparison + EntityTrait, E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>, RichObject: BatchFillableAcceptor<Vec<E::Model>>,
{ {
if let Some(ref obj) = self.object { if let Some(ref obj) = self.object {
let batch =E::find() let batch =E::find()
@ -69,7 +101,7 @@ impl BatchFillable for RichActivity {
mod hell { mod hell {
use sea_orm::{sea_query::IntoCondition, ColumnTrait, ConnectionTrait, DbErr, EntityTrait}; 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 { pub trait BatchFillableComparison {
fn comparison(ids: Vec<i64>) -> sea_orm::Condition; fn comparison(ids: Vec<i64>) -> 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<B> { pub trait BatchFillableAcceptor<B> {
async fn accept(&mut self, batch: B, tx: &impl ConnectionTrait) -> Result<(), DbErr>; async fn accept(&mut self, batch: B, tx: &impl ConnectionTrait) -> Result<(), DbErr>;
} }
impl BatchFillableAcceptor<Vec<crate::model::attachment::Model>> for super::RichActivity { impl BatchFillableAcceptor<Vec<crate::model::attachment::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::attachment::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> { async fn accept(&mut self, batch: Vec<crate::model::attachment::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> {
self.attachments = Some(batch); self.attachments = Some(batch);
Ok(()) Ok(())
} }
} }
impl BatchFillableAcceptor<Vec<crate::model::hashtag::Model>> for super::RichActivity { impl BatchFillableAcceptor<Vec<crate::model::hashtag::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::hashtag::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> { async fn accept(&mut self, batch: Vec<crate::model::hashtag::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> {
self.hashtags = Some(batch.into_iter().map(|x| RichHashtag { hash: x }).collect()); self.hashtags = Some(batch.into_iter().map(|x| RichHashtag { hash: x }).collect());
Ok(()) Ok(())
} }
} }
impl BatchFillableAcceptor<Vec<crate::model::mention::Model>> for super::RichActivity { impl BatchFillableAcceptor<Vec<crate::model::mention::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::mention::Model>, tx: &impl ConnectionTrait) -> Result<(), DbErr> { async fn accept(&mut self, batch: Vec<crate::model::mention::Model>, tx: &impl ConnectionTrait) -> Result<(), DbErr> {
// TODO batch load users from mentions rather than doing for loop // TODO batch load users from mentions rather than doing for loop
let mut mentions = Vec::new(); let mut mentions = Vec::new();

View file

@ -5,8 +5,4 @@ mod query;
pub use query::Query; pub use query::Query;
mod rich; mod rich;
pub use rich::{RichActivity, RichNotification}; pub use rich::{RichActivity, RichObject, RichNotification};

View file

@ -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(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 { if let Some(uid) = my_id {
select = select select = select
.join( .join(

View file

@ -32,23 +32,70 @@ impl IntoActivityPub for RichHashtag {
} }
} }
pub struct RichActivity { pub struct RichObject {
pub activity: Option<crate::model::activity::Model>,
pub object: Option<crate::model::object::Model>, pub object: Option<crate::model::object::Model>,
pub liked: Option<i64>, pub liked: Option<i64>,
pub attachments: Option<Vec<crate::model::attachment::Model>>, pub attachments: Option<Vec<crate::model::attachment::Model>>,
pub hashtags: Option<Vec<RichHashtag>>, pub hashtags: Option<Vec<RichHashtag>>,
pub mentions: Option<Vec<RichMention>>, pub mentions: Option<Vec<RichMention>>,
}
impl FromQueryResult for RichObject {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
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<crate::model::activity::Model>,
pub object: Option<RichObject>,
pub discovered: chrono::DateTime<chrono::Utc>, pub discovered: chrono::DateTime<chrono::Utc>,
} }
impl FromQueryResult for RichActivity { impl FromQueryResult for RichActivity {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> { fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
Ok(RichActivity { Ok(RichActivity {
attachments: None, hashtags: None, mentions: None, object: RichObject::from_query_result_optional(res, _pre)?,
liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(), activity: crate::model::activity::Model::from_query_result_optional(res, crate::model::activity::Entity.table_name())?,
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(),
discovered: res.try_get( discovered: res.try_get(
crate::model::addressing::Entity.table_name(), crate::model::addressing::Entity.table_name(),
&crate::model::addressing::Column::Published.to_string() &crate::model::addressing::Column::Published.to_string()
@ -63,76 +110,18 @@ impl IntoActivityPub for RichActivity {
match (self.activity, self.object) { match (self.activity, self.object) {
(None, None) => serde_json::Value::Null, (None, None) => serde_json::Value::Null,
(Some(activity), None) => { (Some(activity), None) => activity.into_activity_pub_json(ctx),
let obj = apb::Node::maybe_link(activity.object.clone());
activity.into_activity_pub_json(ctx).set_object(obj)
},
(maybe_activity, Some(object)) => { (None, Some(object)) => apb::new()
let mut tags = Vec::new(); .set_activity_type(Some(apb::ActivityType::View))
if let Some(mentions) = self.mentions { .set_published(Some(self.discovered))
for mention in mentions { .set_object(apb::Node::object(object.into_activity_pub_json(ctx))),
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));
}
}
let activity = match maybe_activity { (Some(activity), Some(object)) => activity
Some(activity) => activity.into_activity_pub_json(ctx), .into_activity_pub_json(ctx)
None => apb::new() .set_object(apb::Node::object(object.into_activity_pub_json(ctx))),
.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()
),
})
))
},
} }
} }
// // 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 { pub struct RichNotification {

View file

@ -1,6 +1,6 @@
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, Order, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect}; 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}; use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity};
@ -36,7 +36,7 @@ pub async fn page(
.order_by(model::object::Column::Published, Order::Asc) .order_by(model::object::Column::Published, Order::Asc)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.into_model::<RichActivity>() .into_model::<RichObject>()
.all(ctx.db()) .all(ctx.db())
.await? .await?
.with_batched::<upub::model::attachment::Entity>(ctx.db()) .with_batched::<upub::model::attachment::Entity>(ctx.db())