1
0
Fork 0
forked from alemi/upub

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 std::collections::{hash_map::Entry, HashMap};
use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter}; use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter};
use super::{RichActivity, RichObject}; use super::RichActivity;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait BatchFillable: Sized { pub trait BatchFillable: Sized {
@ -9,8 +9,7 @@ pub trait BatchFillable: Sized {
where where
E: BatchFillableComparison + EntityTrait, E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>, RichActivity: BatchFillableAcceptor<Vec<E::Model>>;
RichObject: BatchFillableAcceptor<Vec<E::Model>>;
} }
@ -22,7 +21,6 @@ impl BatchFillable for Vec<RichActivity> {
E: BatchFillableComparison + EntityTrait, E: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>, 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 ids : Vec<i64> = self.iter().filter_map(|x| Some(x.object.as_ref()?.internal)).collect();
let batch = E::find() 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] #[async_trait::async_trait]
impl BatchFillable for RichActivity { impl BatchFillable for RichActivity {
async fn with_batched<E>(mut self, tx: &impl ConnectionTrait) -> Result<Self, DbErr> 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: BatchFillableComparison + EntityTrait,
E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>, E::Model: BatchFillableKey + Send + FromQueryResult + ModelTrait<Entity = E>,
RichActivity: BatchFillableAcceptor<Vec<E::Model>>, RichActivity: BatchFillableAcceptor<Vec<E::Model>>,
RichObject: BatchFillableAcceptor<Vec<E::Model>>
{ {
if let Some(ref obj) = self.object { if let Some(ref obj) = self.object {
let batch =E::find() 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 // welcome to interlocking trait hell, enjoy your stay
mod hell { mod hell {
@ -207,42 +155,7 @@ use crate::selector::rich::{RichHashtag, RichMention};
Ok(()) 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::*; use hell::*;

View file

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

View file

@ -4,13 +4,13 @@ use crate::model;
pub struct Query; pub struct Query;
impl 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() let mut select = model::addressing::Entity::find()
.distinct_on([ .distinct_on([
(model::addressing::Entity, model::addressing::Column::Published).into_column_ref(), (model::addressing::Entity, model::addressing::Column::Published).into_column_ref(),
(model::activity::Entity, model::activity::Column::Internal).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()) .join(sea_orm::JoinType::LeftJoin, model::addressing::Relation::Objects.def())
.filter( .filter(
// TODO ghetto double inner join because i want to filter out tombstones // TODO ghetto double inner join because i want to filter out tombstones
@ -43,34 +43,6 @@ impl Query {
select 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> { pub fn related(from: Option<i64>, to: Option<i64>, pending: bool) -> Select<model::relation::Entity> {
let mut condition = Condition::all(); let mut condition = Condition::all();

View file

@ -31,7 +31,7 @@ impl RichHashtag {
} }
pub struct RichActivity { pub struct RichActivity {
pub activity: crate::model::activity::Model, pub activity: Option<crate::model::activity::Model>,
pub object: Option<crate::model::object::Model>, pub object: Option<crate::model::object::Model>,
pub liked: Option<i64>, pub liked: Option<i64>,
pub attachments: Option<Vec<crate::model::attachment::Model>>, pub attachments: Option<Vec<crate::model::attachment::Model>>,
@ -42,21 +42,27 @@ pub struct RichActivity {
impl FromQueryResult for RichActivity { impl FromQueryResult for RichActivity {
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> { fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
Ok(RichActivity { 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, 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 { impl RichActivity {
pub fn ap(self) -> serde_json::Value { pub fn ap(self) -> serde_json::Value {
use apb::ObjectMut; use apb::ObjectMut;
let object = match self.object { match (self.activity, self.object) {
None => apb::Node::maybe_link(self.activity.object.clone()), (None, None) => serde_json::Value::Null,
Some(o) => {
// TODO can we avoid repeating this tags code? (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(); let mut tags = Vec::new();
if let Some(mentions) = self.mentions { if let Some(mentions) = self.mentions {
for mention in mentions { for mention in mentions {
@ -68,10 +74,35 @@ impl RichActivity {
tags.push(hash.ap()); tags.push(hash.ap());
} }
} }
apb::Node::object( object.ap()
o.ap()
.set_liked_by_me(if self.liked.is_some() { Some(true) } else { None }) .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 { .set_attachment(match self.attachments {
None => apb::Node::Empty, None => apb::Node::Empty,
Some(vec) => apb::Node::array( 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(model::activity::Column::Id.eq(&aid))
.filter(auth.filter_activities()) .filter(auth.filter_activities())
.into_model::<RichActivity>() .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()); return Err(crate::ApiError::forbidden());
} }
crate::builders::paginate_activities( crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/inbox/page"), upub::url!(ctx, "/actors/{id}/inbox/page"),
upub::model::addressing::Column::Actor.eq(*internal).into_condition(), upub::model::addressing::Column::Actor.eq(*internal).into_condition(),
ctx.db(), ctx.db(),

View file

@ -1,8 +1,6 @@
pub mod inbox; pub mod inbox;
pub mod outbox; pub mod outbox;
pub mod following; pub mod following;
pub mod feed;
pub mod streams;
use axum::extract::{Path, Query, State}; use axum::extract::{Path, Query, State};
@ -58,7 +56,6 @@ pub async fn view(
let mut user = user_model.ap() let mut user = user_model.ap()
.set_inbox(Node::link(upub::url!(ctx, "/actors/{id}/inbox"))) .set_inbox(Node::link(upub::url!(ctx, "/actors/{id}/inbox")))
.set_outbox(Node::link(upub::url!(ctx, "/actors/{id}/outbox"))) .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_following(Node::link(upub::url!(ctx, "/actors/{id}/following")))
.set_followers(Node::link(upub::url!(ctx, "/actors/{id}/followers"))) .set_followers(Node::link(upub::url!(ctx, "/actors/{id}/followers")))
.set_following_me(following_me) .set_following_me(following_me)
@ -93,8 +90,6 @@ pub async fn view(
user_model.ap() user_model.ap()
.set_following_me(following_me) .set_following_me(following_me)
.set_followed_by_me(followed_by_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() .ld_context()
)), )),
None => Err(crate::ApiError::not_found()), None => Err(crate::ApiError::not_found()),

View file

@ -19,7 +19,7 @@ pub async fn page(
AuthIdentity(auth): AuthIdentity, AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let uid = ctx.uid(&id); let uid = ctx.uid(&id);
crate::builders::paginate_activities( crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/outbox/page"), upub::url!(ctx, "/actors/{id}/outbox/page"),
Condition::all() Condition::all()
.add(auth.filter_activities()) .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_actor_type(Some(apb::ActorType::Application))
.set_name(Some(&ctx.cfg().instance.name)) .set_name(Some(&ctx.cfg().instance.name))
.set_summary(Some(&ctx.cfg().instance.description)) .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_inbox(apb::Node::link(upub::url!(ctx, "/inbox")))
.set_outbox(apb::Node::link(upub::url!(ctx, "/outbox"))) .set_outbox(apb::Node::link(upub::url!(ctx, "/outbox")))
.set_published(Some(ctx.actor().published)) .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, AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>, Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_activities( crate::builders::paginate_feed(
upub::url!(ctx, "/inbox/page"), upub::url!(ctx, "/inbox/page"),
auth.filter_activities(), auth.filter_activities(),
ctx.db(), ctx.db(),

View file

@ -1,7 +1,6 @@
pub mod actor; pub mod actor;
pub mod inbox; pub mod inbox;
pub mod outbox; pub mod outbox;
pub mod feed;
pub mod object; pub mod object;
pub mod activity; pub mod activity;
pub mod application; pub mod application;
@ -32,8 +31,6 @@ impl ActivityPubRouter for Router<upub::Context> {
.route("/outbox", post(ap::outbox::post)) .route("/outbox", post(ap::outbox::post))
.route("/outbox", get(ap::outbox::get)) .route("/outbox", get(ap::outbox::get))
.route("/outbox/page", get(ap::outbox::page)) .route("/outbox/page", get(ap::outbox::page))
.route("/feed", get(ap::feed::get))
.route("/feed/page", get(ap::feed::page))
// AUTH routes // AUTH routes
.route("/auth", put(ap::auth::register)) .route("/auth", put(ap::auth::register))
.route("/auth", post(ap::auth::login)) .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", post(ap::actor::outbox::post))
.route("/actors/:id/outbox", get(ap::actor::outbox::get)) .route("/actors/:id/outbox", get(ap::actor::outbox::get))
.route("/actors/:id/outbox/page", get(ap::actor::outbox::page)) .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", get(ap::actor::following::get::<false>))
.route("/actors/:id/followers/page", get(ap::actor::following::page::<false>)) .route("/actors/:id/followers/page", get(ap::actor::following::page::<false>))
.route("/actors/:id/following", get(ap::actor::following::get::<true>)) .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>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let context = ctx.oid(&id); 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(auth.filter_objects())
.filter(model::object::Column::Context.eq(&context)) .filter(model::object::Column::Context.eq(&context))
.count(ctx.db()) .count(ctx.db())
@ -28,7 +28,7 @@ pub async fn page(
) -> crate::ApiResult<JsonLD<serde_json::Value>> { ) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let context = ctx.oid(&id); let context = ctx.oid(&id);
crate::builders::paginate_objects( crate::builders::paginate_feed(
upub::url!(ctx, "/objects/{id}/context/page"), upub::url!(ctx, "/objects/{id}/context/page"),
Condition::all() Condition::all()
.add(auth.filter_objects()) .add(auth.filter_objects())

View file

@ -4,7 +4,7 @@ pub mod context;
use apb::{BaseMut, CollectionMut, ObjectMut, LD}; use apb::{BaseMut, CollectionMut, 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, 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}; 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(model::object::Column::Id.eq(&oid))
.filter(auth.filter_objects()) .filter(auth.filter_objects())
.into_model::<RichObject>() .into_model::<RichActivity>()
.one(ctx.db()) .one(ctx.db())
.await? .await?
.ok_or_else(crate::ApiError::not_found)? .ok_or_else(crate::ApiError::not_found)?
@ -45,7 +45,7 @@ pub async fn view(
let mut replies = apb::Node::Empty; let mut replies = apb::Node::Empty;
if ctx.cfg().security.show_reply_ids { 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(model::object::Column::InReplyTo.eq(oid))
.filter(auth.filter_objects()) .filter(auth.filter_objects())
.select_only() .select_only()
@ -59,7 +59,7 @@ pub async fn view(
.set_id(Some(&upub::url!(ctx, "/objects/{id}/replies"))) .set_id(Some(&upub::url!(ctx, "/objects/{id}/replies")))
.set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page"))) .set_first(apb::Node::link(upub::url!(ctx, "/objects/{id}/replies/page")))
.set_collection_type(Some(apb::CollectionType::Collection)) .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)) .set_items(apb::Node::links(replies_ids))
); );
} }

View file

@ -15,7 +15,7 @@ pub async fn get(
// ctx.fetch_thread(&oid).await?; // 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(model::object::Column::InReplyTo.eq(ctx.oid(&id)))
.filter(auth.filter_objects()) .filter(auth.filter_objects())
.select_only() .select_only()
@ -44,7 +44,7 @@ pub async fn page(
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);
crate::builders::paginate_objects( crate::builders::paginate_feed(
page_id, page_id,
Condition::all() Condition::all()
.add(auth.filter_objects()) .add(auth.filter_objects())

View file

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

View file

@ -1,11 +1,11 @@
use apb::{BaseMut, CollectionMut, CollectionPageMut, LD}; use apb::{BaseMut, CollectionMut, CollectionPageMut, LD};
use sea_orm::{Condition, ConnectionTrait, QueryFilter, QuerySelect, RelationTrait}; use sea_orm::{Condition, ConnectionTrait, QueryFilter, QuerySelect, RelationTrait};
use axum::response::{IntoResponse, Response}; use axum::response::{IntoResponse, Response};
use upub::selector::{BatchFillable, RichActivity, RichObject}; use upub::selector::{BatchFillable, RichActivity};
use crate::activitypub::Pagination; use crate::activitypub::Pagination;
pub async fn paginate_activities( pub async fn paginate_feed(
id: String, id: String,
filter: Condition, filter: Condition,
db: &impl ConnectionTrait, db: &impl ConnectionTrait,
@ -16,7 +16,7 @@ pub async fn paginate_activities(
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);
let mut select = upub::Query::activities(my_id); let mut select = upub::Query::feed(my_id);
if with_users { if with_users {
select = select select = select
@ -46,52 +46,6 @@ pub async fn paginate_activities(
collection_page(&id, offset, limit, items) 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>> { 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 { let next = if items.len() < limit as usize {
apb::Node::Empty apb::Node::Empty