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
This commit is contained in:
əlemi 2024-04-21 23:19:26 +02:00
parent c595f5f5e3
commit cc287d3aa4
Signed by: alemi
GPG key ID: A4895B84D311642C
7 changed files with 82 additions and 60 deletions

View file

@ -1,4 +1,4 @@
use apb::{ActivityMut, Node}; use apb::{ActivityMut, ObjectMut};
use sea_orm::{entity::prelude::*, FromQueryResult, Iterable, QuerySelect, SelectColumns}; use sea_orm::{entity::prelude::*, FromQueryResult, Iterable, QuerySelect, SelectColumns};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
@ -67,12 +67,22 @@ pub struct EmbeddedActivity {
pub object: Option<crate::model::object::Model>, pub object: Option<crate::model::object::Model>,
} }
impl From<EmbeddedActivity> for serde_json::Value { impl EmbeddedActivity {
fn from(value: EmbeddedActivity) -> Self { pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result<serde_json::Value> {
let a = value.activity.ap(); let a = self.activity.ap();
match value.object { match self.object {
None => a, None => Ok(a),
Some(o) => a.set_object(Node::object(o.ap())), 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, pub object: crate::model::object::Model,
} }
impl From<WrappedObject> for serde_json::Value {
fn from(value: WrappedObject) -> Self { impl WrappedObject {
match value.activity { pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result<serde_json::Value> {
None => value.object.ap(), let attachments = self.object.find_related(crate::model::attachment::Entity)
Some(a) => a.ap().set_object( .all(db)
Node::object(value.object.ap()) .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))),
} }
} }
} }

View file

@ -26,7 +26,9 @@ pub async fn view(
.one(ctx.db()) .one(ctx.db())
.await? .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()), None => Err(UpubError::not_found()),
} }
} }

View file

@ -28,14 +28,14 @@ pub async fn page(
.into_model::<EmbeddedActivity>() .into_model::<EmbeddedActivity>()
.all(ctx.db()) .all(ctx.db())
.await?; .await?;
let mut out = Vec::new();
for activity in activities {
out.push(activity.ap_filled(ctx.db()).await?);
}
Ok(JsonLD( Ok(JsonLD(
ctx.ap_collection_page( ctx.ap_collection_page(
&url!(ctx, "/inbox/page"), &url!(ctx, "/inbox/page"),
offset, limit, offset, limit, out,
activities
.into_iter()
.map(|x| x.into())
.collect()
).ld_context() ).ld_context()
)) ))
} }

View file

@ -2,7 +2,7 @@ pub mod replies;
use apb::{BaseMut, CollectionMut, ObjectMut}; use apb::{BaseMut, CollectionMut, ObjectMut};
use axum::extract::{Path, Query, State}; 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}}; 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()); 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::<Vec<serde_json::Value>>();
let replies = let replies =
serde_json::Value::new_object() serde_json::Value::new_object()
.set_id(Some(&crate::url!(ctx, "/objects/{id}/replies"))) .set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
@ -43,6 +50,7 @@ pub async fn view(
Ok(JsonLD( Ok(JsonLD(
object.object.ap() object.object.ap()
.set_replies(apb::Node::object(replies)) .set_replies(apb::Node::object(replies))
.set_attachment(apb::Node::Array(attachments))
.ld_context() .ld_context()
)) ))
} }

View file

@ -11,33 +11,31 @@ pub async fn page(
State(ctx): State<Context>, State(ctx): State<Context>,
Query(page): Query<Pagination>, Query(page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity, AuthIdentity(auth): AuthIdentity,
) -> Result<JsonLD<serde_json::Value>, StatusCode> { ) -> crate::Result<JsonLD<serde_json::Value>> {
let limit = page.batch.unwrap_or(20).min(50); let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0); let offset = page.offset.unwrap_or(0);
match model::addressing::Entity::find_activities() let items = model::addressing::Entity::find_activities()
.filter(auth.filter_condition()) .filter(auth.filter_condition())
// TODO also limit to only local activities // TODO also limit to only local activities
.order_by(model::addressing::Column::Published, Order::Desc) .order_by(model::addressing::Column::Published, Order::Desc)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.into_model::<EmbeddedActivity>() .into_model::<EmbeddedActivity>()
.all(ctx.db()).await .all(ctx.db()).await?;
{
Err(_e) => Err(StatusCode::INTERNAL_SERVER_ERROR), let mut out = Vec::new();
Ok(items) => { for item in items {
out.push(item.ap_filled(ctx.db()).await?);
}
Ok(JsonLD( Ok(JsonLD(
ctx.ap_collection_page( ctx.ap_collection_page(
&url!(ctx, "/outbox/page"), &url!(ctx, "/outbox/page"),
offset, limit, offset, limit,
items out,
.into_iter()
.map(|x| x.into())
.collect()
).ld_context() ).ld_context()
)) ))
},
}
} }
pub async fn post( pub async fn post(

View file

@ -42,14 +42,14 @@ pub async fn page(
.into_model::<EmbeddedActivity>() .into_model::<EmbeddedActivity>()
.all(ctx.db()) .all(ctx.db())
.await?; .await?;
let mut out = Vec::new();
for activity in activities {
out.push(activity.ap_filled(ctx.db()).await?);
}
Ok(JsonLD( Ok(JsonLD(
ctx.ap_collection_page( ctx.ap_collection_page(
&url!(ctx, "/users/{id}/inbox/page"), &url!(ctx, "/users/{id}/inbox/page"),
offset, limit, offset, limit, out,
activities
.into_iter()
.map(|x| x.into())
.collect()
).ld_context() ).ld_context()
)) ))
} }

View file

@ -18,7 +18,7 @@ pub async fn page(
Path(id): Path<String>, Path(id): Path<String>,
Query(page): Query<Pagination>, Query(page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity, AuthIdentity(auth): AuthIdentity,
) -> Result<JsonLD<serde_json::Value>, StatusCode> { ) -> crate::Result<JsonLD<serde_json::Value>> {
let uid = if id.starts_with('+') { let uid = if id.starts_with('+') {
format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) format!("https://{}", id.replacen('+', "", 1).replace('@', "/"))
} else { } else {
@ -27,29 +27,26 @@ pub async fn page(
let limit = page.batch.unwrap_or(20).min(50); let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0); 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(model::activity::Column::Actor.eq(&uid))
.filter(auth.filter_condition()) .filter(auth.filter_condition())
.order_by(model::addressing::Column::Published, Order::Desc) .order_by(model::addressing::Column::Published, Order::Desc)
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.into_model::<EmbeddedActivity>() .into_model::<EmbeddedActivity>()
.all(ctx.db()).await .all(ctx.db()).await?;
{
Err(_e) => Err(StatusCode::INTERNAL_SERVER_ERROR), let mut out = Vec::new();
Ok(items) => { for activity in activities {
out.push(activity.ap_filled(ctx.db()).await?);
}
Ok(JsonLD( Ok(JsonLD(
ctx.ap_collection_page( ctx.ap_collection_page(
&url!(ctx, "/users/{id}/outbox/page"), &url!(ctx, "/users/{id}/outbox/page"),
offset, limit, offset, limit, out,
items
.into_iter()
.map(|x| x.into())
.collect()
).ld_context() ).ld_context()
)) ))
},
}
} }
pub async fn post( pub async fn post(