From cc287d3aa4fca6f82ea38a07015bd25d5f9b04b6 Mon Sep 17 00:00:00 2001 From: alemi Date: Sun, 21 Apr 2024 23:19:26 +0200 Subject: [PATCH] feat: show attachments in inbox, outbox and /object attachments are lazy loaded, so it may be efficient if not all posts have media, but it should probably be eager loaded anyway eventually --- src/model/addressing.rs | 45 ++++++++++++++++++--------- src/routes/activitypub/activity.rs | 4 ++- src/routes/activitypub/inbox.rs | 10 +++--- src/routes/activitypub/object/mod.rs | 10 +++++- src/routes/activitypub/outbox.rs | 32 +++++++++---------- src/routes/activitypub/user/inbox.rs | 10 +++--- src/routes/activitypub/user/outbox.rs | 31 +++++++++--------- 7 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/model/addressing.rs b/src/model/addressing.rs index cc46eca..ad928c6 100644 --- a/src/model/addressing.rs +++ b/src/model/addressing.rs @@ -1,4 +1,4 @@ -use apb::{ActivityMut, Node}; +use apb::{ActivityMut, ObjectMut}; use sea_orm::{entity::prelude::*, FromQueryResult, Iterable, QuerySelect, SelectColumns}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -67,12 +67,22 @@ pub struct EmbeddedActivity { pub object: Option, } -impl From for serde_json::Value { - fn from(value: EmbeddedActivity) -> Self { - let a = value.activity.ap(); - match value.object { - None => a, - Some(o) => a.set_object(Node::object(o.ap())), +impl EmbeddedActivity { + pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result { + let a = self.activity.ap(); + match self.object { + None => Ok(a), + Some(o) => { + let attachments = o.find_related(crate::model::attachment::Entity) + .all(db) + .await? + .into_iter() + .map(|x| x.ap()) + .collect(); + Ok(a.set_object( + apb::Node::object(o.ap().set_attachment(apb::Node::array(attachments))) + )) + } } } } @@ -91,13 +101,20 @@ pub struct WrappedObject { pub object: crate::model::object::Model, } -impl From for serde_json::Value { - fn from(value: WrappedObject) -> Self { - match value.activity { - None => value.object.ap(), - Some(a) => a.ap().set_object( - Node::object(value.object.ap()) - ), + +impl WrappedObject { + pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result { + let attachments = self.object.find_related(crate::model::attachment::Entity) + .all(db) + .await? + .into_iter() + .map(|x| x.ap()) + .collect(); + let o = self.object.ap() + .set_attachment(apb::Node::Array(attachments)); + match self.activity { + None => Ok(o), + Some(a) => Ok(a.ap().set_object(apb::Node::object(o))), } } } diff --git a/src/routes/activitypub/activity.rs b/src/routes/activitypub/activity.rs index a7bb6ff..c53053d 100644 --- a/src/routes/activitypub/activity.rs +++ b/src/routes/activitypub/activity.rs @@ -26,7 +26,9 @@ pub async fn view( .one(ctx.db()) .await? { - Some(activity) => Ok(JsonLD(serde_json::Value::from(activity).ld_context())), + Some(activity) => Ok(JsonLD( + activity.ap_filled(ctx.db()).await?.ld_context() + )), None => Err(UpubError::not_found()), } } diff --git a/src/routes/activitypub/inbox.rs b/src/routes/activitypub/inbox.rs index 3b2adde..152a3b1 100644 --- a/src/routes/activitypub/inbox.rs +++ b/src/routes/activitypub/inbox.rs @@ -28,14 +28,14 @@ pub async fn page( .into_model::() .all(ctx.db()) .await?; + let mut out = Vec::new(); + for activity in activities { + out.push(activity.ap_filled(ctx.db()).await?); + } Ok(JsonLD( ctx.ap_collection_page( &url!(ctx, "/inbox/page"), - offset, limit, - activities - .into_iter() - .map(|x| x.into()) - .collect() + offset, limit, out, ).ld_context() )) } diff --git a/src/routes/activitypub/object/mod.rs b/src/routes/activitypub/object/mod.rs index f264962..d47ef4d 100644 --- a/src/routes/activitypub/object/mod.rs +++ b/src/routes/activitypub/object/mod.rs @@ -2,7 +2,7 @@ pub mod replies; use apb::{BaseMut, CollectionMut, ObjectMut}; use axum::extract::{Path, Query, State}; -use sea_orm::{ColumnTrait, QueryFilter}; +use sea_orm::{ColumnTrait, ModelTrait, QueryFilter}; use crate::{errors::UpubError, model::{self, addressing::WrappedObject}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}}; @@ -33,6 +33,13 @@ pub async fn view( return Err(UpubError::not_found()); }; + let attachments = object.object.find_related(model::attachment::Entity) + .all(ctx.db()) + .await? + .into_iter() + .map(|x| x.ap()) + .collect::>(); + let replies = serde_json::Value::new_object() .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies"))) @@ -43,6 +50,7 @@ pub async fn view( Ok(JsonLD( object.object.ap() .set_replies(apb::Node::object(replies)) + .set_attachment(apb::Node::Array(attachments)) .ld_context() )) } diff --git a/src/routes/activitypub/outbox.rs b/src/routes/activitypub/outbox.rs index 9823ff6..9393e6d 100644 --- a/src/routes/activitypub/outbox.rs +++ b/src/routes/activitypub/outbox.rs @@ -11,33 +11,31 @@ pub async fn page( State(ctx): State, Query(page): Query, AuthIdentity(auth): AuthIdentity, -) -> Result, StatusCode> { +) -> crate::Result> { let limit = page.batch.unwrap_or(20).min(50); let offset = page.offset.unwrap_or(0); - match model::addressing::Entity::find_activities() + let items = model::addressing::Entity::find_activities() .filter(auth.filter_condition()) // TODO also limit to only local activities .order_by(model::addressing::Column::Published, Order::Desc) .limit(limit) .offset(offset) .into_model::() - .all(ctx.db()).await - { - Err(_e) => Err(StatusCode::INTERNAL_SERVER_ERROR), - Ok(items) => { - Ok(JsonLD( - ctx.ap_collection_page( - &url!(ctx, "/outbox/page"), - offset, limit, - items - .into_iter() - .map(|x| x.into()) - .collect() - ).ld_context() - )) - }, + .all(ctx.db()).await?; + + let mut out = Vec::new(); + for item in items { + out.push(item.ap_filled(ctx.db()).await?); } + + Ok(JsonLD( + ctx.ap_collection_page( + &url!(ctx, "/outbox/page"), + offset, limit, + out, + ).ld_context() + )) } pub async fn post( diff --git a/src/routes/activitypub/user/inbox.rs b/src/routes/activitypub/user/inbox.rs index 9992382..6729ddd 100644 --- a/src/routes/activitypub/user/inbox.rs +++ b/src/routes/activitypub/user/inbox.rs @@ -42,14 +42,14 @@ pub async fn page( .into_model::() .all(ctx.db()) .await?; + let mut out = Vec::new(); + for activity in activities { + out.push(activity.ap_filled(ctx.db()).await?); + } Ok(JsonLD( ctx.ap_collection_page( &url!(ctx, "/users/{id}/inbox/page"), - offset, limit, - activities - .into_iter() - .map(|x| x.into()) - .collect() + offset, limit, out, ).ld_context() )) } diff --git a/src/routes/activitypub/user/outbox.rs b/src/routes/activitypub/user/outbox.rs index 127a00f..5c311d5 100644 --- a/src/routes/activitypub/user/outbox.rs +++ b/src/routes/activitypub/user/outbox.rs @@ -18,7 +18,7 @@ pub async fn page( Path(id): Path, Query(page): Query, AuthIdentity(auth): AuthIdentity, -) -> Result, StatusCode> { +) -> crate::Result> { let uid = if id.starts_with('+') { format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) } else { @@ -27,29 +27,26 @@ pub async fn page( let limit = page.batch.unwrap_or(20).min(50); let offset = page.offset.unwrap_or(0); - match model::addressing::Entity::find_activities() + let activities = model::addressing::Entity::find_activities() .filter(model::activity::Column::Actor.eq(&uid)) .filter(auth.filter_condition()) .order_by(model::addressing::Column::Published, Order::Desc) .limit(limit) .offset(offset) .into_model::() - .all(ctx.db()).await - { - Err(_e) => Err(StatusCode::INTERNAL_SERVER_ERROR), - Ok(items) => { - Ok(JsonLD( - ctx.ap_collection_page( - &url!(ctx, "/users/{id}/outbox/page"), - offset, limit, - items - .into_iter() - .map(|x| x.into()) - .collect() - ).ld_context() - )) - }, + .all(ctx.db()).await?; + + let mut out = Vec::new(); + for activity in activities { + out.push(activity.ap_filled(ctx.db()).await?); } + + Ok(JsonLD( + ctx.ap_collection_page( + &url!(ctx, "/users/{id}/outbox/page"), + offset, limit, out, + ).ld_context() + )) } pub async fn post(