fix(routes): proper replies collection

also don't include replies in first object fetch: needless work since
most of the AP world doesn't use them. if you do, explicitly dereference
/replies!
This commit is contained in:
əlemi 2024-12-26 16:13:58 +01:00
parent cc090ccb19
commit 9f8b6b8280
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 61 additions and 53 deletions

View file

@ -1,9 +1,9 @@
pub mod replies; pub mod replies;
pub mod context; pub mod context;
use apb::{BaseMut, CollectionMut, ObjectMut, LD}; use apb::{BaseMut, Object, ObjectMut, LD};
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, QueryFilter, QuerySelect, SelectColumns, TransactionTrait}; use sea_orm::{ColumnTrait, QueryFilter, TransactionTrait};
use upub::{model, selector::{BatchFillable, RichActivity}, traits::Fetcher, Context}; use upub::{model, selector::{BatchFillable, RichActivity}, traits::Fetcher, Context};
use crate::{builders::JsonLD, AuthIdentity}; use crate::{builders::JsonLD, AuthIdentity};
@ -27,7 +27,9 @@ pub async fn view(
} }
} }
let item = upub::Query::objects(auth.my_id()) let replies_url = upub::url!(ctx, "/objects/{id}/replies");
let item_model = upub::Query::objects(auth.my_id())
.filter(auth.filter_objects()) .filter(auth.filter_objects())
.filter(model::object::Column::Id.eq(&oid)) .filter(model::object::Column::Id.eq(&oid))
.into_model::<RichActivity>() .into_model::<RichActivity>()
@ -41,31 +43,17 @@ pub async fn view(
.with_batched::<upub::model::hashtag::Entity>(ctx.db()) .with_batched::<upub::model::hashtag::Entity>(ctx.db())
.await?; .await?;
let mut replies = apb::Node::Empty; let mut item = ctx.ap(item_model);
if ctx.cfg().security.show_reply_ids {
let replies_ids = upub::Query::objects(auth.my_id())
.filter(auth.filter_objects())
.filter(model::object::Column::InReplyTo.eq(oid))
.select_only()
.select_column(model::object::Column::Id)
.into_tuple::<String>()
.all(ctx.db())
.await?;
replies = apb::Node::object( let replies_patched =
apb::new() apb::Node::object(
.set_id(Some(upub::url!(ctx, "/objects/{id}/replies"))) item.replies()
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page"))) .into_inner()
.set_collection_type(Some(apb::CollectionType::Collection)) .unwrap()
.set_total_items(item.object.as_ref().map(|x| x.replies as u64)) .set_id(Some(replies_url))
.set_items(apb::Node::links(replies_ids)) );
);
} item = item.set_replies(replies_patched);
Ok(JsonLD( Ok(JsonLD(item.ld_context()))
item.object_ap()
.set_replies(replies)
.ld_context()
))
} }

View file

@ -1,9 +1,9 @@
use apb::{BaseMut, CollectionMut, LD}; use apb::{BaseMut, CollectionMut, CollectionPageMut, LD};
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, Condition, QueryFilter, QuerySelect, SelectColumns}; use sea_orm::{ColumnTrait, ConnectionTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
use upub::{model, traits::Fetcher, Context}; use upub::{model, traits::Fetcher, Context};
use crate::{activitypub::{Pagination, TryFetch}, builders::JsonLD, AuthIdentity}; use crate::{activitypub::{Pagination, TryFetch}, builders::JsonLD, ApiResult, AuthIdentity, Identity};
pub async fn get( pub async fn get(
State(ctx): State<Context>, State(ctx): State<Context>,
@ -21,22 +21,21 @@ pub async fn get(
ctx.fetch_thread(&oid, ctx.db()).await?; ctx.fetch_thread(&oid, ctx.db()).await?;
} }
let replies_ids = upub::Query::objects(auth.my_id()) let replies_count = total_replies(&oid, &auth, ctx.db()).await?;
.filter(auth.filter_objects()) let replies_ids = replies_ids(&oid, &auth, ctx.db(), 20, 0).await?;
.filter(model::object::Column::InReplyTo.eq(ctx.oid(&id)))
.select_only() let first = apb::new()
.select_column(model::object::Column::Id) .set_id(Some(upub::url!(ctx, "/objects/{id}/replies/page")))
.into_tuple::<String>() .set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
.all(ctx.db()) .set_next(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page?offset=20")))
.await?; .set_ordered_items(apb::Node::links(replies_ids));
Ok(JsonLD( Ok(JsonLD(
apb::new() apb::new()
.set_id(Some(upub::url!(ctx, "/objects/{id}/replies"))) .set_id(Some(upub::url!(ctx, "/objects/{id}/replies")))
.set_collection_type(Some(apb::CollectionType::Collection)) .set_collection_type(Some(apb::CollectionType::Collection))
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page"))) .set_total_items(Some(replies_count))
.set_total_items(Some(replies_ids.len() as u64)) .set_first(apb::Node::object(first))
.set_items(apb::Node::links(replies_ids))
.ld_context() .ld_context()
)) ))
} }
@ -49,19 +48,40 @@ pub async fn page(
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let page_id = upub::url!(ctx, "/objects/{id}/replies/page"); let page_id = upub::url!(ctx, "/objects/{id}/replies/page");
let oid = ctx.oid(&id); let oid = ctx.oid(&id);
let (limit, offset) = page.pagination();
// TODO kinda weird ignoring this but its weirder to exclude replies from replies view... // TODO kinda weird ignoring this but its weirder to exclude replies from replies view...
page.replies = Some(true); page.replies = Some(true);
crate::builders::paginate_feed( let replies_ids = replies_ids(&oid, &auth, ctx.db(), limit, offset).await?;
page_id,
Condition::all() crate::builders::collection_page(
.add(auth.filter_activities()) &page_id,
.add(model::object::Column::InReplyTo.eq(oid)), offset,
ctx.db(), limit,
page, apb::Node::links(replies_ids)
auth.my_id(),
false,
) )
.await }
async fn replies_ids(oid: &str, auth: &Identity, db: &impl ConnectionTrait, limit: u64, offset: u64) -> ApiResult<Vec<String>> {
let res = upub::Query::objects(auth.my_id())
.limit(limit)
.offset(offset)
.filter(auth.filter_objects())
.filter(model::object::Column::InReplyTo.eq(oid))
.select_only()
.select_column(model::object::Column::Id)
.into_tuple::<String>()
.all(db)
.await?;
Ok(res)
}
async fn total_replies(oid: &str, auth: &Identity, db: &impl ConnectionTrait) -> ApiResult<u64> {
let count = upub::Query::objects(None)
.filter(auth.filter_objects())
.filter(model::object::Column::InReplyTo.eq(oid))
.count(db)
.await?;
Ok(count)
} }