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:
parent
c595f5f5e3
commit
cc287d3aa4
7 changed files with 82 additions and 60 deletions
|
@ -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))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue