chore: finally rid us of builders::paginate_feed

for good
This commit is contained in:
əlemi 2024-12-26 19:47:51 +01:00
parent 437908f7d5
commit 129213204a
Signed by: alemi
GPG key ID: A4895B84D311642C
18 changed files with 194 additions and 204 deletions

View file

@ -3,6 +3,27 @@ use std::collections::{hash_map::Entry, HashMap};
use sea_orm::{ConnectionTrait, DbErr, EntityTrait, FromQueryResult, ModelTrait, QueryFilter};
use super::{RichActivity, RichObject};
#[allow(async_fn_in_trait)]
pub trait RichFillable: Sized {
async fn load_batched_models(self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>;
}
impl<T> RichFillable for T
where
T: BatchFillable
{
async fn load_batched_models(self, tx: &impl ConnectionTrait) -> Result<Self, DbErr> {
self
.with_batched::<crate::model::attachment::Entity>(tx)
.await?
.with_batched::<crate::model::mention::Entity>(tx)
.await?
.with_batched::<crate::model::hashtag::Entity>(tx)
.await
}
}
#[allow(async_fn_in_trait)]
pub trait BatchFillable: Sized {
async fn with_batched<E>(self, tx: &impl ConnectionTrait) -> Result<Self, DbErr>

View file

@ -1,5 +1,5 @@
mod batch;
pub use batch::BatchFillable;
pub use batch::{BatchFillable, RichFillable};
mod query;
pub use query::Query;

View file

@ -4,7 +4,7 @@ use crate::model;
pub struct Query;
impl Query {
pub fn feed(my_id: Option<i64>) -> Select<model::addressing::Entity> {
pub fn feed(my_id: Option<i64>, with_replies: bool) -> Select<model::addressing::Entity> {
let mut select = model::addressing::Entity::find()
.distinct_on([
(model::addressing::Entity, model::addressing::Column::Published).into_column_ref(),
@ -45,10 +45,14 @@ impl Query {
.select_column_as(model::like::Column::Actor, format!("{}{}", model::like::Entity.table_name(), model::like::Column::Actor.to_string()));
}
if !with_replies {
select = select.filter(model::object::Column::InReplyTo.is_null());
}
select
}
pub fn objects(my_id: Option<i64>) -> Select<model::addressing::Entity> {
pub fn objects(my_id: Option<i64>, with_replies: bool) -> Select<model::addressing::Entity> {
let mut select = model::addressing::Entity::find()
.distinct()
.join(sea_orm::JoinType::InnerJoin, model::addressing::Relation::Objects.def())
@ -68,6 +72,10 @@ impl Query {
.select_column_as(model::like::Column::Actor, format!("{}{}", model::like::Entity.table_name(), model::like::Column::Actor.to_string()));
}
if !with_replies {
select = select.filter(model::object::Column::InReplyTo.is_null());
}
select
}

View file

@ -1,6 +1,6 @@
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, QueryFilter, TransactionTrait};
use upub::{model, selector::{BatchFillable, RichActivity}, traits::Fetcher, Context};
use sea_orm::{ColumnTrait, Condition, QueryFilter, TransactionTrait};
use upub::{model, selector::{RichActivity, RichFillable}, traits::Fetcher, Context};
use apb::LD;
use crate::{builders::JsonLD, AuthIdentity};
@ -23,20 +23,19 @@ pub async fn view(
}
}
let row = upub::Query::feed(auth.my_id())
.filter(auth.filter_activities())
.filter(model::activity::Column::Id.eq(&aid))
let filter = Condition::all()
.add(auth.filter_activities())
.add(model::activity::Column::Id.eq(&aid));
let activity = upub::Query::feed(auth.my_id(), true)
.filter(filter)
.into_model::<RichActivity>()
.one(ctx.db())
.await?
.ok_or_else(crate::ApiError::not_found)?
.with_batched::<upub::model::attachment::Entity>(ctx.db())
.await?
.with_batched::<upub::model::mention::Entity>(ctx.db())
.await?
.with_batched::<upub::model::hashtag::Entity>(ctx.db())
.load_batched_models(ctx.db())
.await?;
Ok(JsonLD(ctx.ap(row).ld_context()))
Ok(JsonLD(ctx.ap(activity).ld_context()))
}

View file

@ -59,7 +59,7 @@ pub async fn page<const OUTGOING: bool>(
use upub::model::relation::Column::{Follower, Following, FollowerInstance, FollowingInstance};
let follow___ = if OUTGOING { "following" } else { "followers" };
let (limit, offset) = page.pagination();
let (limit, _offset) = page.pagination();
let (user, config) = model::actor::Entity::find_by_ap_id(&ctx.uid(&id))
.find_also_related(model::config::Entity)
@ -117,7 +117,7 @@ pub async fn page<const OUTGOING: bool>(
crate::builders::collection_page(
&upub::url!(ctx, "/actors/{id}/{follow___}/page"),
offset, limit,
page,
apb::Node::links(following),
)
}

View file

@ -1,7 +1,7 @@
use axum::{http::StatusCode, extract::{Path, Query, State}, Json};
use sea_orm::{ColumnTrait, Condition};
use sea_orm::{ColumnTrait, Condition, QueryFilter, QuerySelect};
use upub::Context;
use upub::{selector::{RichActivity, RichFillable}, Context};
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity, Identity};
@ -35,18 +35,26 @@ pub async fn page(
return Err(crate::ApiError::forbidden());
}
crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/inbox/page"),
Condition::any()
.add(upub::model::addressing::Column::Actor.eq(*internal))
.add(upub::model::activity::Column::Actor.eq(uid))
.add(upub::model::object::Column::AttributedTo.eq(uid)),
&ctx,
page,
auth.my_id(),
false,
)
.await
let filter = Condition::any()
.add(upub::model::addressing::Column::Actor.eq(*internal))
.add(upub::model::activity::Column::Actor.eq(uid))
.add(upub::model::object::Column::AttributedTo.eq(uid));
let (limit, offset) = page.pagination();
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/inbox/page"), page, apb::Node::array(items))
}
pub async fn post(

View file

@ -31,15 +31,13 @@ pub async fn page(
let (limit, offset) = page.pagination();
let mut select = upub::Query::objects(auth.my_id())
let items : Vec<serde_json::Value> = upub::Query::objects(auth.my_id(), true)
.join(sea_orm::JoinType::InnerJoin, upub::model::object::Relation::Likes.def())
.filter(auth.filter_objects())
.filter(upub::model::like::Column::Actor.eq(user.internal))
.order_by_desc(upub::model::like::Column::Published)
.limit(limit)
.offset(offset);
let items : Vec<serde_json::Value> = select
.offset(offset)
.into_model::<RichObject>()
.all(ctx.db())
.await?
@ -52,14 +50,6 @@ pub async fn page(
.into_iter()
.map(|x| ctx.ap(x))
.collect();
crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/outbox/page"),
auth.filter_objects(),
&ctx,
page,
auth.my_id(),
false,
)
.await
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/outbox/page"), page, apb::Node::array(items))
}

View file

@ -51,6 +51,6 @@ pub async fn page(
.map(|x| ctx.ap(x))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/notifications/page"), offset, limit, apb::Node::array(activities))
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/notifications/page"), page, apb::Node::array(activities))
}

View file

@ -1,7 +1,7 @@
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
use sea_orm::{ActiveValue::{NotSet, Set}, ColumnTrait, Condition, EntityTrait};
use sea_orm::{ActiveValue::{NotSet, Set}, ColumnTrait, Condition, EntityTrait, QueryFilter, QuerySelect};
use upub::{model, Context};
use upub::{model, selector::{RichActivity, RichFillable}, Context};
use crate::{activitypub::{CreationResult, Pagination}, builders::JsonLD, AuthIdentity, Identity};
@ -28,15 +28,23 @@ pub async fn page(
.add(model::object::Column::Audience.eq(&uid))
);
crate::builders::paginate_feed(
upub::url!(ctx, "/actors/{id}/outbox/page"),
filter,
&ctx,
page,
auth.my_id(),
false,
)
.await
let (limit, offset) = page.pagination();
// by default we want replies because servers don't know about our api and need to see everything
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
// TODO also limit to only local activities
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/outbox/page"), page, apb::Node::array(items))
}
pub async fn post(

View file

@ -1,8 +1,8 @@
use apb::{LD, ActorMut, BaseMut, ObjectMut, PublicKeyMut};
use axum::{extract::{Path, Query, State}, http::HeaderMap, response::{IntoResponse, Redirect, Response}};
use reqwest::Method;
use sea_orm::{Condition, ColumnTrait};
use upub::{traits::{Cloaker, Fetcher}, Context};
use sea_orm::{ColumnTrait, Condition, QueryFilter, QuerySelect};
use upub::{selector::{RichFillable, RichObject}, traits::{Cloaker, Fetcher}, Context};
use crate::{builders::JsonLD, ApiError, AuthIdentity};
@ -57,21 +57,28 @@ pub async fn search(
// TODO lmao rethink this all
// still haven't redone this gg me
// have redone it but didnt rethink it properly so we're stuck with this bahahaha
let page = Pagination {
offset: page.offset,
batch: page.batch,
replies: Some(true),
};
crate::builders::paginate_feed(
upub::url!(ctx, "/search"),
filter,
&ctx,
page,
auth.my_id(),
false,
)
.await
let (limit, offset) = page.pagination();
let items = upub::Query::feed(auth.my_id(), true)
.filter(filter)
.limit(limit)
.offset(offset)
.into_model::<RichObject>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/search"), page, apb::Node::array(items))
}
#[derive(Debug, serde::Deserialize)]

View file

@ -1,7 +1,7 @@
use apb::{Activity, ActivityType, Base};
use axum::{extract::{Query, State}, http::StatusCode, Json};
use sea_orm::{sea_query::IntoCondition, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait};
use upub::{model::job::JobType, Context};
use sea_orm::{sea_query::IntoCondition, ActiveValue::{NotSet, Set}, ColumnTrait, EntityTrait, QueryFilter, QuerySelect};
use upub::{model::job::JobType, selector::{RichActivity, RichFillable}, Context};
use crate::{AuthIdentity, Identity, builders::JsonLD};
@ -19,24 +19,23 @@ pub async fn page(
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_feed(
upub::url!(ctx, "/inbox/page"),
upub::model::addressing::Column::Actor.is_null().into_condition(),
&ctx,
page,
auth.my_id(),
false,
)
.await
let filter = upub::model::addressing::Column::Actor.is_null().into_condition();
let (limit, offset) = page.pagination();
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/inbox/page"), page, apb::Node::array(items))
}
macro_rules! pretty_json {
($json:ident) => {
serde_json::to_string_pretty(&$json).expect("failed serializing to string serde_json::Value")
}
}
pub async fn post(
State(ctx): State<Context>,
AuthIdentity(auth): AuthIdentity,
@ -52,7 +51,10 @@ pub async fn post(
// would be cool if mastodon played nicer with the network...
return Ok(StatusCode::OK);
}
tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity));
tracing::warn!(
"refusing unauthorized activity: {}",
serde_json::to_string_pretty(&activity).expect("failed serializing to string serde_json::Value?")
);
if matches!(auth, Identity::Anonymous) {
return Err(crate::ApiError::unauthorized());
} else {

View file

@ -88,7 +88,7 @@ pub struct TryFetch {
pub fetch: bool,
}
#[derive(Debug, serde::Deserialize)]
#[derive(Debug, Clone, Copy, serde::Deserialize)]
// TODO i don't really like how pleroma/mastodon do it actually, maybe change this?
pub struct Pagination {
pub offset: Option<u64>,

View file

@ -1,6 +1,6 @@
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
use upub::{model, selector::{BatchFillable, RichObject}, Context};
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter, QuerySelect};
use upub::{model, selector::{RichFillable, RichObject}, Context};
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity};
@ -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::objects(auth.my_id(), true)
.filter(auth.filter_objects())
.filter(model::object::Column::Context.eq(&context))
.count(ctx.db())
@ -23,33 +23,30 @@ pub async fn get(
pub async fn page(
State(ctx): State<Context>,
Path(id): Path<String>,
Query(page): Query<Pagination>,
Query(mut page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let context = ctx.oid(&id);
let filter = Condition::all()
.add(auth.filter_objects())
.add(model::object::Column::Context.eq(context));
page.replies = Some(true); // TODO ugly that we have to force set it this way...
let (limit, offset) = page.pagination();
let items = upub::Query::objects(auth.my_id())
.filter(auth.filter_objects())
.filter(model::object::Column::Context.eq(context))
// note that this should be ASC so we get replies somewhat ordered
.order_by_asc(model::object::Column::Published)
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
.limit(limit)
.offset(offset)
.into_model::<RichObject>()
.all(ctx.db())
.await?
.with_batched::<upub::model::attachment::Entity>(ctx.db())
.load_batched_models(ctx.db())
.await?
.with_batched::<upub::model::mention::Entity>(ctx.db())
.await?
.with_batched::<upub::model::hashtag::Entity>(ctx.db())
.await?;
let items : Vec<serde_json::Value> = items
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/objects/{id}/context/page"), offset, limit, apb::Node::array(items))
crate::builders::collection_page(&upub::url!(ctx, "/objects/{id}/context/page"), page, apb::Node::array(items))
}

View file

@ -3,8 +3,8 @@ pub mod context;
use apb::LD;
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, QueryFilter, TransactionTrait};
use upub::{model, selector::{BatchFillable, RichObject}, traits::Fetcher, Context};
use sea_orm::{ColumnTrait, Condition, QueryFilter, TransactionTrait};
use upub::{model, selector::{RichFillable, RichObject}, traits::Fetcher, Context};
use crate::{builders::JsonLD, AuthIdentity};
@ -27,19 +27,18 @@ pub async fn view(
}
}
let item = upub::Query::objects(auth.my_id())
.filter(auth.filter_objects())
.filter(model::object::Column::Id.eq(&oid))
let filter = Condition::all()
.add(auth.filter_objects())
.add(model::object::Column::Id.eq(&oid));
let object = upub::Query::feed(auth.my_id(), true)
.filter(filter)
.into_model::<RichObject>()
.one(ctx.db())
.await?
.ok_or_else(crate::ApiError::not_found)?
.with_batched::<upub::model::attachment::Entity>(ctx.db())
.await?
.with_batched::<upub::model::mention::Entity>(ctx.db())
.await?
.with_batched::<upub::model::hashtag::Entity>(ctx.db())
.load_batched_models(ctx.db())
.await?;
Ok(JsonLD(ctx.ap(item).ld_context()))
Ok(JsonLD(ctx.ap(object).ld_context()))
}

View file

@ -1,7 +1,7 @@
use apb::{BaseMut, CollectionMut, LD};
use axum::extract::{Path, Query, State};
use sea_orm::{ColumnTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
use upub::{model, selector::RichObject, traits::Fetcher, Context};
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter, QuerySelect};
use upub::{model, selector::{RichFillable, RichObject}, traits::Fetcher, Context};
use crate::{activitypub::{Pagination, TryFetch}, builders::JsonLD, AuthIdentity};
@ -21,7 +21,7 @@ pub async fn get(
ctx.fetch_thread(&oid, ctx.db()).await?;
}
let total_replies = upub::Query::objects(None)
let total_replies = upub::Query::objects(None, true)
.filter(auth.filter_objects())
.filter(model::object::Column::InReplyTo.eq(&oid))
.count(ctx.db())
@ -43,30 +43,29 @@ pub async fn page(
Query(mut page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let page_id = upub::url!(ctx, "/objects/{id}/replies/page");
let oid = ctx.oid(&id);
let (limit, offset) = page.pagination();
// TODO kinda weird ignoring this but its weirder to exclude replies from replies view...
page.replies = Some(true);
let res = upub::Query::objects(auth.my_id())
let filter = Condition::all()
.add(auth.filter_objects())
.add(model::object::Column::InReplyTo.eq(oid));
let (limit, offset) = page.pagination();
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
// TODO also limit to only local activities
.limit(limit)
.offset(offset)
.filter(auth.filter_objects())
.filter(model::object::Column::InReplyTo.eq(oid))
.order_by_desc(model::object::Column::Published)
.into_model::<RichObject>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|x| ctx.ap(x))
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(
&page_id,
offset,
limit,
apb::Node::array(res)
)
crate::builders::collection_page(&upub::url!(ctx, "/objects/{id}/replies/page"), page, apb::Node::array(items))
}

View file

@ -1,6 +1,6 @@
use axum::{extract::{Query, State}, http::StatusCode, Json};
use sea_orm::{ColumnTrait, Condition};
use upub::Context;
use sea_orm::{ColumnTrait, Condition, QueryFilter, QuerySelect};
use upub::{selector::{RichActivity, RichFillable}, Context};
use crate::{activitypub::{CreationResult, Pagination}, AuthIdentity, builders::JsonLD};
@ -13,17 +13,25 @@ pub async fn page(
Query(page): Query<Pagination>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::paginate_feed(
upub::url!(ctx, "/outbox/page"),
Condition::all()
.add(upub::model::addressing::Column::Actor.is_null())
.add(upub::model::actor::Column::Domain.eq(ctx.domain().to_string())),
&ctx,
page,
auth.my_id(),
true,
)
.await
let filter = Condition::all()
.add(upub::model::addressing::Column::Actor.is_null())
.add(upub::model::actor::Column::Domain.eq(ctx.domain().to_string()));
let (limit, offset) = page.pagination();
let items = upub::Query::feed(auth.my_id(), page.replies.unwrap_or(true))
.filter(filter)
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|item| ctx.ap(item))
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/outbox/page"), page, apb::Node::array(items))
}
pub async fn post(

View file

@ -1,7 +1,7 @@
use axum::extract::{Path, Query, State};
use sea_orm::{QueryFilter, QuerySelect, ColumnTrait};
use upub::{selector::{BatchFillable, RichActivity}, Context};
use upub::{selector::{RichFillable, RichActivity}, Context};
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity};
@ -31,11 +31,7 @@ pub async fn page(
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.with_batched::<upub::model::attachment::Entity>(ctx.db())
.await?
.with_batched::<upub::model::mention::Entity>(ctx.db())
.await?
.with_batched::<upub::model::hashtag::Entity>(ctx.db())
.load_batched_models(ctx.db())
.await?
.into_iter()
.map(|x| ctx.ap(x))
@ -43,8 +39,7 @@ pub async fn page(
crate::builders::collection_page(
&upub::url!(ctx, "/tags/{id}/page"),
offset,
limit,
page,
apb::Node::array(objects),
)

View file

@ -1,61 +1,10 @@
use apb::{BaseMut, CollectionMut, CollectionPageMut, LD};
use sea_orm::{Condition, QueryFilter, QuerySelect, RelationTrait, ColumnTrait};
use axum::response::{IntoResponse, Response};
use upub::selector::{BatchFillable, RichActivity};
use crate::activitypub::Pagination;
#[deprecated = "use upub::Query directly"]
pub async fn paginate_feed(
id: String,
filter: Condition,
ctx: &upub::Context,
page: Pagination,
my_id: Option<i64>,
with_users: bool, // TODO ewww too many arguments for this weird function...
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
pub fn collection_page(id: &str, page: Pagination, items: apb::Node<serde_json::Value>) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let (limit, offset) = page.pagination();
let mut conditions = Condition::all()
.add(filter);
// by default we want replies because servers don't know about our api and want everything
if !page.replies.unwrap_or(true) {
conditions = conditions.add(upub::model::object::Column::InReplyTo.is_null());
}
let mut select = upub::Query::feed(my_id);
if with_users {
select = select
.join(sea_orm::JoinType::InnerJoin, upub::model::activity::Relation::Actors.def());
}
let db = ctx.db();
let items = select
.filter(conditions)
// TODO also limit to only local activities
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.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| ctx.ap(item))
.collect();
collection_page(&id, offset, limit, apb::Node::array(items))
}
pub fn collection_page(id: &str, offset: u64, limit: u64, items: apb::Node<serde_json::Value>) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let next = if items.len() < limit as usize {
apb::Node::Empty
} else {