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};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
@ -67,12 +67,22 @@ pub struct EmbeddedActivity {
pub object: Option<crate::model::object::Model>,
}
impl From<EmbeddedActivity> 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<serde_json::Value> {
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<WrappedObject> 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<serde_json::Value> {
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))),
}
}
}

View file

@ -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()),
}
}

View file

@ -28,14 +28,14 @@ pub async fn page(
.into_model::<EmbeddedActivity>()
.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()
))
}

View file

@ -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::<Vec<serde_json::Value>>();
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()
))
}

View file

@ -11,33 +11,31 @@ pub async fn page(
State(ctx): State<Context>,
Query(page): Query<Pagination>,
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 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::<EmbeddedActivity>()
.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(

View file

@ -42,14 +42,14 @@ pub async fn page(
.into_model::<EmbeddedActivity>()
.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()
))
}

View file

@ -18,7 +18,7 @@ pub async fn page(
Path(id): Path<String>,
Query(page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity,
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
) -> crate::Result<JsonLD<serde_json::Value>> {
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::<EmbeddedActivity>()
.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(