feat: removed object timelines

it's back to original concept babyyyy activities all the way down
This commit is contained in:
əlemi 2024-06-28 00:12:03 +02:00
parent 3698d1947d
commit 727e977c4e
Signed by: alemi
GPG key ID: A4895B84D311642C
19 changed files with 72 additions and 371 deletions

View file

@ -1,7 +1,7 @@
use std::collections::{hash_map::Entry, HashMap};
use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter};
use super::{RichActivity, RichObject};
use super::RichActivity;
#[async_trait::async_trait]
pub trait BatchFillable: Sized {
@ -9,8 +9,7 @@ pub trait BatchFillable: Sized {
where
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>;
RichActivity: BatchFillableAcceptor<Vec<E::Model>>;
}
@ -22,7 +21,6 @@ impl BatchFillable for Vec<RichActivity> {
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{
let ids : Vec<i64> = self.iter().filter_map(|x| Some(x.object.as_ref()?.internal)).collect();
let batch = E::find()
@ -47,37 +45,6 @@ impl BatchFillable for Vec<RichActivity> {
}
}
#[async_trait::async_trait]
impl BatchFillable for Vec<RichObject> {
// TODO 3 iterations... can we make it in less passes?
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{
let ids : Vec<i64> = self.iter().map(|x| x.object.internal).collect();
let batch = E::find()
.filter(E::comparison(ids))
.all(tx)
.await?;
let mut map : HashMap<i64, Vec<E::Model>> = HashMap::new();
for element in batch {
match map.entry(element.key()) {
Entry::Occupied(mut x) => { x.get_mut().push(element); },
Entry::Vacant(x) => { x.insert(vec![element]); },
}
}
for element in self.iter_mut() {
if let Some(v) = map.remove(&element.object.internal) {
element.accept(v, tx).await?;
}
}
Ok(self)
}
}
#[async_trait::async_trait]
impl BatchFillable for RichActivity {
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
@ -85,7 +52,6 @@ impl BatchFillable for RichActivity {
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{
if let Some(ref obj) = self.object {
let batch =E::find()
@ -98,24 +64,6 @@ impl BatchFillable for RichActivity {
}
}
#[async_trait::async_trait]
impl BatchFillable for RichObject {
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>
where
E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{
let batch = E::find()
.filter(E::comparison(vec![self.object.internal]))
.all(tx)
.await?;
self.accept(batch, tx).await?;
Ok(self)
}
}
// welcome to interlocking trait hell, enjoy your stay
mod hell {
@ -207,42 +155,7 @@ use crate::selector::rich::{RichHashtag, RichMention};
Ok(())
}
}
#[async_trait::async_trait]
impl BatchFillableAcceptor<Vec<crate::model::attachment::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::attachment::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> {
self.attachments = Some(batch);
Ok(())
}
}
#[async_trait::async_trait]
impl BatchFillableAcceptor<Vec<crate::model::hashtag::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::hashtag::Model>, _tx: &impl ConnectionTrait) -> Result<(), DbErr> {
self.hashtags = Some(batch.into_iter().map(|x| RichHashtag { hash: x }).collect());
Ok(())
}
}
#[async_trait::async_trait]
impl BatchFillableAcceptor<Vec<crate::model::mention::Model>> for super::RichObject {
async fn accept(&mut self, batch: Vec<crate::model::mention::Model>, tx: &impl ConnectionTrait) -> Result<(), DbErr> {
// TODO batch load users from mentions rather than doing for loop
let mut mentions = Vec::new();
for row in batch {
// TODO filter only needed rows
if let Some(user) = crate::model::actor::Entity::find_by_id(row.actor).one(tx).await? {
mentions.push(RichMention {
mention: row,
fqn: format!("@{}@{}", user.preferred_username, user.domain),
id: user.id,
});
}
}
self.mentions = Some(mentions);
Ok(())
}
}
}
use hell::*;

View file

@ -5,7 +5,7 @@ mod query;
pub use query::Query;
mod rich;
pub use rich::{RichObject, RichActivity};
pub use rich::RichActivity;

View file

@ -4,13 +4,13 @@ use crate::model;
pub struct Query;
impl Query {
pub fn activities(my_id: Option<i64>) -> Select<model::addressing::Entity> {
pub fn feed(my_id: Option<i64>) -> Select<model::addressing::Entity> {
let mut select = model::addressing::Entity::find()
.distinct_on([
(model::addressing::Entity, model::addressing::Column::Published).into_column_ref(),
(model::activity::Entity, model::activity::Column::Internal).into_column_ref(),
])
.join(sea_orm::JoinType::InnerJoin, model::addressing::Relation::Activities.def())
.join(sea_orm::JoinType::LeftJoin, model::addressing::Relation::Activities.def())
.join(sea_orm::JoinType::LeftJoin, model::addressing::Relation::Objects.def())
.filter(
// TODO ghetto double inner join because i want to filter out tombstones
@ -43,34 +43,6 @@ impl Query {
select
}
pub fn objects(my_id: Option<i64>) -> Select<model::addressing::Entity> {
let mut select = model::addressing::Entity::find()
.distinct_on([
(model::addressing::Entity, model::addressing::Column::Published).into_column_ref(),
(model::object::Entity, model::object::Column::Internal).into_column_ref(),
])
.join(sea_orm::JoinType::InnerJoin, model::addressing::Relation::Objects.def())
.order_by(model::addressing::Column::Published, Order::Desc)
.order_by(model::object::Column::Internal, Order::Desc)
.select_only();
for col in model::object::Column::iter() {
select = select.select_column_as(col, format!("{}{}", model::object::Entity.table_name(), col.to_string()));
}
if let Some(uid) = my_id {
select = select
.join(
sea_orm::JoinType::LeftJoin,
model::object::Relation::Likes.def()
.on_condition(move |_l, _r| model::like::Column::Actor.eq(uid).into_condition()),
)
.select_column_as(model::like::Column::Actor, format!("{}{}", model::like::Entity.table_name(), model::like::Column::Actor.to_string()));
}
select
}
pub fn related(from: Option<i64>, to: Option<i64>, pending: bool) -> Select<model::relation::Entity> {
let mut condition = Condition::all();

View file

@ -31,7 +31,7 @@ impl RichHashtag {
}
pub struct RichActivity {
pub activity: crate::model::activity::Model,
pub activity: Option<crate::model::activity::Model>,
pub object: Option<crate::model::object::Model>,
pub liked: Option<i64>,
pub attachments: Option<Vec<crate::model::attachment::Model>>,
@ -42,21 +42,27 @@ pub struct RichActivity {
impl FromQueryResult for RichActivity {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
Ok(RichActivity {
activity: crate::model::activity::Model::from_query_result(res, crate::model::activity::Entity.table_name())?,
object: crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name()).ok(),
liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(),
attachments: None, hashtags: None, mentions: None,
liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(),
object: crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name()).ok(),
activity: crate::model::activity::Model::from_query_result(res, crate::model::activity::Entity.table_name()).ok(),
})
}
}
// TODO avoid repeating the tags code twice
impl RichActivity {
pub fn ap(self) -> serde_json::Value {
use apb::ObjectMut;
let object = match self.object {
None => apb::Node::maybe_link(self.activity.object.clone()),
Some(o) => {
// TODO can we avoid repeating this tags code?
match (self.activity, self.object) {
(None, None) => serde_json::Value::Null,
(Some(activity), None) => {
let obj = apb::Node::maybe_link(activity.object.clone());
activity.ap().set_object(obj)
},
(None, Some(object)) => {
let mut tags = Vec::new();
if let Some(mentions) = self.mentions {
for mention in mentions {
@ -68,10 +74,35 @@ impl RichActivity {
tags.push(hash.ap());
}
}
apb::Node::object(
o.ap()
object.ap()
.set_liked_by_me(if self.liked.is_some() { Some(true) } else { None })
.set_tag(apb::Node::array(tags))
.set_tag(apb::Node::maybe_array(tags))
.set_attachment(match self.attachments {
None => apb::Node::Empty,
Some(vec) => apb::Node::array(
vec.into_iter().map(|x| x.ap()).collect()
),
})
},
(Some(activity), Some(object)) => {
let mut tags = Vec::new();
if let Some(mentions) = self.mentions {
for mention in mentions {
tags.push(mention.ap());
}
}
if let Some(hashtags) = self.hashtags {
for hash in hashtags {
tags.push(hash.ap());
}
}
activity.ap()
.set_object(
apb::Node::object(
object.ap()
.set_liked_by_me(if self.liked.is_some() { Some(true) } else { None })
.set_tag(apb::Node::maybe_array(tags))
.set_attachment(match self.attachments {
None => apb::Node::Empty,
Some(vec) => apb::Node::array(
@ -79,53 +110,8 @@ impl RichActivity {
),
})
)
},
};
self.activity.ap().set_object(object)
}
}
pub struct RichObject {
pub object: crate::model::object::Model,
pub liked: Option<i64>,
pub attachments: Option<Vec<crate::model::attachment::Model>>,
pub hashtags: Option<Vec<RichHashtag>>,
pub mentions: Option<Vec<RichMention>>,
}
impl FromQueryResult for RichObject {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
Ok(RichObject {
object: crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name())?,
liked: res.try_get(crate::model::like::Entity.table_name(), &crate::model::like::Column::Actor.to_string()).ok(),
attachments: None, hashtags: None, mentions: None,
})
}
}
impl RichObject {
pub fn ap(self) -> serde_json::Value {
use apb::ObjectMut;
// TODO can we avoid repeating this tags code?
let mut tags = Vec::new();
if let Some(mentions) = self.mentions {
for mention in mentions {
tags.push(mention.ap());
}
}
if let Some(hashtags) = self.hashtags {
for hash in hashtags {
tags.push(hash.ap());
}
}
self.object.ap()
.set_liked_by_me(if self.liked.is_some() { Some(true) } else { None })
.set_tag(apb::Node::array(tags))
.set_attachment(match self.attachments {
None => apb::Node::Empty,
Some(vec) => apb::Node::array(
vec.into_iter().map(|x| x.ap()).collect()
)
})
},
}
}
}

View file

@ -23,7 +23,7 @@ pub async fn view(
}
}
let row = upub::Query::activities(auth.my_id())
let row = upub::Query::feed(auth.my_id())
.filter(model::activity::Column::Id.eq(&aid))
.filter(auth.filter_activities())
.into_model::<RichActivity>()

View file

@ -1,47 +0,0 @@
use axum::extract::{Path, Query, State};
use sea_orm::{sea_query::IntoCondition, ColumnTrait};
use upub::Context;
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity, Identity};
pub async fn get(
State(ctx): State<Context>,
Path(id): Path<String>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
match auth {
Identity::Anonymous => Err(crate::ApiError::forbidden()),
Identity::Remote { .. } => Err(crate::ApiError::forbidden()),
Identity::Local { id: user, .. } => if ctx.uid(&id) == user {
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/feed"), None)
} else {
Err(crate::ApiError::forbidden())
},
}
}
pub async fn page(
State(ctx): State<Context>,
Path(id): Path<String>,
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let Identity::Local { id: uid, internal } = &auth else {
// local inbox is only for local users
return Err(crate::ApiError::forbidden());
};
if uid != &ctx.uid(&id) {
return Err(crate::ApiError::forbidden());
}
crate::builders::paginate_objects(
upub::url!(ctx, "/actors/{id}/feed/page"),
upub::model::addressing::Column::Actor.eq(*internal).into_condition(),
ctx.db(),
page,
auth.my_id(),
false,
)
.await
}

View file

@ -35,7 +35,7 @@ pub async fn page(
return Err(crate::ApiError::forbidden());
}
crate::builders::paginate_activities(
crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/inbox/page"),
upub::model::addressing::Column::Actor.eq(*internal).into_condition(),
ctx.db(),

View file

@ -1,8 +1,6 @@
pub mod inbox;
pub mod outbox;
pub mod following;
pub mod feed;
pub mod streams;
use axum::extract::{Path, Query, State};
@ -58,7 +56,6 @@ pub async fn view(
let mut user = user_model.ap()
.set_inbox(Node::link(upub::url!(ctx, "/actors/{id}/inbox")))
.set_outbox(Node::link(upub::url!(ctx, "/actors/{id}/outbox")))
.set_streams(Node::link(upub::url!(ctx, "/actors/{id}/streams")))
.set_following(Node::link(upub::url!(ctx, "/actors/{id}/following")))
.set_followers(Node::link(upub::url!(ctx, "/actors/{id}/followers")))
.set_following_me(following_me)
@ -93,8 +90,6 @@ pub async fn view(
user_model.ap()
.set_following_me(following_me)
.set_followed_by_me(followed_by_me)
// TODO should we set streams?? we offer this collection but actor is remote
.set_streams(Node::link(upub::url!(ctx, "/actors/{id}/streams")))
.ld_context()
)),
None => Err(crate::ApiError::not_found()),

View file

@ -19,7 +19,7 @@ pub async fn page(
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let uid = ctx.uid(&id);
crate::builders::paginate_activities(
crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/outbox/page"),
Condition::all()
.add(auth.filter_activities())

View file

@ -1,32 +0,0 @@
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, Condition};
use upub::Context;
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity};
pub async fn get(
State(ctx): State<Context>,
Path(id): Path<String>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/streams"), None)
}
pub async fn page(
State(ctx): State<Context>,
Path(id): Path<String>,
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_objects(
upub::url!(ctx, "/actors/{id}/streams/page"),
Condition::all()
.add(auth.filter_objects())
.add(upub::model::object::Column::AttributedTo.eq(ctx.uid(&id))),
ctx.db(),
page,
auth.my_id(),
false,
)
.await
}

View file

@ -25,10 +25,6 @@ pub async fn view(
.set_actor_type(Some(apb::ActorType::Application))
.set_name(Some(&ctx.cfg().instance.name))
.set_summary(Some(&ctx.cfg().instance.description))
.set_streams(apb::Node::links(vec![
upub::url!(ctx, "/feed"),
upub::url!(ctx, "/local"),
]))
.set_inbox(apb::Node::link(upub::url!(ctx, "/inbox")))
.set_outbox(apb::Node::link(upub::url!(ctx, "/outbox")))
.set_published(Some(ctx.actor().published))

View file

@ -1,29 +0,0 @@
use axum::extract::{Query, State};
use upub::Context;
use crate::{AuthIdentity, builders::JsonLD};
use super::Pagination;
pub async fn get(
State(ctx): State<Context>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(&upub::url!(ctx, "/feed"), None)
}
pub async fn page(
State(ctx): State<Context>,
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_objects(
upub::url!(ctx, "/feed/page"),
auth.filter_objects(),
ctx.db(),
page,
auth.my_id(),
false,
)
.await
}

View file

@ -19,7 +19,7 @@ pub async fn page(
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_activities(
crate::builders::paginate_feed(
upub::url!(ctx, "/inbox/page"),
auth.filter_activities(),
ctx.db(),

View file

@ -1,7 +1,6 @@
pub mod actor;
pub mod inbox;
pub mod outbox;
pub mod feed;
pub mod object;
pub mod activity;
pub mod application;
@ -32,8 +31,6 @@ impl ActivityPubRouter for Router<upub::Context> {
.route("/outbox", post(ap::outbox::post))
.route("/outbox", get(ap::outbox::get))
.route("/outbox/page", get(ap::outbox::page))
.route("/feed", get(ap::feed::get))
.route("/feed/page", get(ap::feed::page))
// AUTH routes
.route("/auth", put(ap::auth::register))
.route("/auth", post(ap::auth::login))
@ -52,10 +49,6 @@ impl ActivityPubRouter for Router<upub::Context> {
.route("/actors/:id/outbox", post(ap::actor::outbox::post))
.route("/actors/:id/outbox", get(ap::actor::outbox::get))
.route("/actors/:id/outbox/page", get(ap::actor::outbox::page))
.route("/actors/:id/streams", get(ap::actor::streams::get))
.route("/actors/:id/streams/page", get(ap::actor::streams::page))
.route("/actors/:id/feed", get(ap::actor::feed::get))
.route("/actors/:id/feed/page", get(ap::actor::feed::page))
.route("/actors/:id/followers", get(ap::actor::following::get::<false>))
.route("/actors/:id/followers/page", get(ap::actor::following::page::<false>))
.route("/actors/:id/following", get(ap::actor::following::get::<true>))

View file

@ -11,7 +11,7 @@ pub async fn get(
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let context = ctx.oid(&id);
let count = upub::Query::objects(auth.my_id())
let count = upub::Query::feed(auth.my_id())
.filter(auth.filter_objects())
.filter(model::object::Column::Context.eq(&context))
.count(ctx.db())
@ -28,7 +28,7 @@ pub async fn page(
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let context = ctx.oid(&id);
crate::builders::paginate_objects(
crate::builders::paginate_feed(
upub::url!(ctx, "/objects/{id}/context/page"),
Condition::all()
.add(auth.filter_objects())

View file

@ -4,7 +4,7 @@ pub mod context;
use apb::{BaseMut, CollectionMut, ObjectMut, LD};
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, QueryFilter, QuerySelect, SelectColumns, TransactionTrait};
use upub::{model, selector::{BatchFillable, RichObject}, traits::Fetcher, Context};
use upub::{model, selector::{BatchFillable, RichActivity}, traits::Fetcher, Context};
use crate::{builders::JsonLD, AuthIdentity};
@ -27,10 +27,10 @@ pub async fn view(
}
}
let item = upub::Query::objects(auth.my_id())
let item = upub::Query::feed(auth.my_id())
.filter(model::object::Column::Id.eq(&oid))
.filter(auth.filter_objects())
.into_model::<RichObject>()
.into_model::<RichActivity>()
.one(ctx.db())
.await?
.ok_or_else(crate::ApiError::not_found)?
@ -45,7 +45,7 @@ pub async fn view(
let mut replies = apb::Node::Empty;
if ctx.cfg().security.show_reply_ids {
let replies_ids = upub::Query::objects(auth.my_id())
let replies_ids = upub::Query::feed(auth.my_id())
.filter(model::object::Column::InReplyTo.eq(oid))
.filter(auth.filter_objects())
.select_only()
@ -59,7 +59,7 @@ pub async fn view(
.set_id(Some(&upub::url!(ctx, "/objects/{id}/replies")))
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page")))
.set_collection_type(Some(apb::CollectionType::Collection))
.set_total_items(Some(item.object.replies as u64))
.set_total_items(item.object.as_ref().map(|x| x.replies as u64))
.set_items(apb::Node::links(replies_ids))
);
}

View file

@ -15,7 +15,7 @@ pub async fn get(
// ctx.fetch_thread(&oid).await?;
// }
let replies_ids = upub::Query::objects(auth.my_id())
let replies_ids = upub::Query::feed(auth.my_id())
.filter(model::object::Column::InReplyTo.eq(ctx.oid(&id)))
.filter(auth.filter_objects())
.select_only()
@ -44,7 +44,7 @@ pub async fn page(
let page_id = upub::url!(ctx, "/objects/{id}/replies/page");
let oid = ctx.oid(&id);
crate::builders::paginate_objects(
crate::builders::paginate_feed(
page_id,
Condition::all()
.add(auth.filter_objects())

View file

@ -13,7 +13,7 @@ pub async fn page(
Query(page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_activities(
crate::builders::paginate_feed(
upub::url!(ctx, "/outbox/page"),
Condition::all()
.add(auth.filter_activities())

View file

@ -1,11 +1,11 @@
use apb::{BaseMut, CollectionMut, CollectionPageMut, LD};
use sea_orm::{Condition, ConnectionTrait, QueryFilter, QuerySelect, RelationTrait};
use axum::response::{IntoResponse, Response};
use upub::selector::{BatchFillable, RichActivity, RichObject};
use upub::selector::{BatchFillable, RichActivity};
use crate::activitypub::Pagination;
pub async fn paginate_activities(
pub async fn paginate_feed(
id: String,
filter: Condition,
db: &impl ConnectionTrait,
@ -16,7 +16,7 @@ pub async fn paginate_activities(
let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0);
let mut select = upub::Query::activities(my_id);
let mut select = upub::Query::feed(my_id);
if with_users {
select = select
@ -46,52 +46,6 @@ pub async fn paginate_activities(
collection_page(&id, offset, limit, items)
}
// TODO can we merge these two??? there are basically only three differences
pub async fn paginate_objects(
id: String,
filter: Condition,
db: &impl ConnectionTrait,
page: Pagination,
my_id: Option<i64>,
with_users: bool, // TODO ewww too many arguments for this weird function...
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0);
let mut select = upub::Query::objects(my_id); // <--- difference one
if with_users {
select = select
.join(
sea_orm::JoinType::InnerJoin,
upub::model::object::Relation::Actors.def() // <--- difference two
);
}
let items = select
.filter(filter)
// TODO also limit to only local activities
.limit(limit)
.offset(offset)
.into_model::<RichObject>() // <--- difference three
.all(db)
.await?
.with_batched::<upub::model::attachment::Entity>(db)
.await?
.with_batched::<upub::model::mention::Entity>(db)
.await?
.with_batched::<upub::model::hashtag::Entity>(db)
.await?;
let items : Vec<serde_json::Value> = items
.into_iter()
.map(|item| item.ap())
.collect();
collection_page(&id, offset, limit, items)
}
pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let next = if items.len() < limit as usize {
apb::Node::Empty