fix: big refactor of timelines querying
should not be more reliable, consistent and all around less buggy, but it may actually break some things so let's find out
This commit is contained in:
parent
165bf19f8c
commit
768081c251
15 changed files with 298 additions and 316 deletions
|
@ -1,5 +1,7 @@
|
|||
use apb::{ActivityMut, ObjectMut};
|
||||
use sea_orm::{entity::prelude::*, FromQueryResult, Iterable, Order, QueryOrder, QuerySelect, SelectColumns};
|
||||
use sea_orm::{entity::prelude::*, Condition, FromQueryResult, Iterable, Order, QueryOrder, QuerySelect, SelectColumns};
|
||||
|
||||
use crate::routes::activitypub::jsonld::LD;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "addressing")]
|
||||
|
@ -59,105 +61,81 @@ impl ActiveModelBehavior for ActiveModel {}
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EmbeddedActivity {
|
||||
pub activity: crate::model::activity::Model,
|
||||
pub object: Option<crate::model::object::Model>,
|
||||
#[allow(clippy::large_enum_variant)] // tombstone is an outlier, not the norm! this is a beefy enum
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
Tombstone,
|
||||
StrayObject(crate::model::object::Model),
|
||||
Activity(crate::model::activity::Model),
|
||||
DeepActivity {
|
||||
activity: crate::model::activity::Model,
|
||||
object: crate::model::object::Model,
|
||||
}
|
||||
}
|
||||
|
||||
impl EmbeddedActivity {
|
||||
pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result<serde_json::Value> {
|
||||
let a = self.activity.ap();
|
||||
match self.object {
|
||||
None => Ok(a),
|
||||
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)))
|
||||
))
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Event::Tombstone => "",
|
||||
Event::StrayObject(x) => x.id.as_str(),
|
||||
Event::Activity(x) => x.id.as_str(),
|
||||
Event::DeepActivity { activity: _, object } => object.id.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ap(self, attachment: Option<Vec<crate::model::attachment::Model>>) -> serde_json::Value {
|
||||
let attachment = match attachment {
|
||||
None => apb::Node::Empty,
|
||||
Some(vec) => apb::Node::array(
|
||||
vec.into_iter().map(|x| x.ap()).collect()
|
||||
),
|
||||
};
|
||||
match self {
|
||||
Event::Activity(x) => x.ap(),
|
||||
Event::DeepActivity { activity, object } =>
|
||||
activity.ap().set_object(apb::Node::object(object.ap().set_attachment(attachment))),
|
||||
Event::StrayObject(x) => serde_json::Value::new_object()
|
||||
.set_activity_type(Some(apb::ActivityType::Activity))
|
||||
.set_object(apb::Node::object(x.ap().set_attachment(attachment))),
|
||||
Event::Tombstone => serde_json::Value::new_object()
|
||||
.set_activity_type(Some(apb::ActivityType::Activity))
|
||||
.set_object(apb::Node::object(
|
||||
serde_json::Value::new_object()
|
||||
.set_object_type(Some(apb::ObjectType::Tombstone))
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromQueryResult for EmbeddedActivity {
|
||||
fn from_query_result(res: &sea_orm::QueryResult, _pre: &str) -> Result<Self, sea_orm::DbErr> {
|
||||
let activity = crate::model::activity::Model::from_query_result(res, crate::model::activity::Entity.table_name())?;
|
||||
let object = crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name()).ok();
|
||||
Ok(Self { activity, object })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WrappedObject {
|
||||
pub activity: Option<crate::model::activity::Model>,
|
||||
pub object: crate::model::object::Model,
|
||||
}
|
||||
|
||||
|
||||
impl WrappedObject {
|
||||
pub async fn ap_filled(self, db: &DatabaseConnection) -> crate::Result<serde_json::Value> {
|
||||
let attachments = self.object.find_related(crate::model::attachment::Entity)
|
||||
.all(db)
|
||||
.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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromQueryResult for WrappedObject {
|
||||
impl FromQueryResult for Event {
|
||||
fn from_query_result(res: &sea_orm::QueryResult, _pre: &str) -> Result<Self, sea_orm::DbErr> {
|
||||
let activity = crate::model::activity::Model::from_query_result(res, crate::model::activity::Entity.table_name()).ok();
|
||||
let object = crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name())?;
|
||||
Ok(Self { activity, object })
|
||||
let object = crate::model::object::Model::from_query_result(res, crate::model::object::Entity.table_name()).ok();
|
||||
match (activity, object) {
|
||||
(Some(activity), Some(object)) => Ok(Self::DeepActivity { activity, object }),
|
||||
(Some(activity), None) => Ok(Self::Activity(activity)),
|
||||
(None, Some(object)) => Ok(Self::StrayObject(object)),
|
||||
(None, None) => Ok(Self::Tombstone),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
impl Entity {
|
||||
pub fn find_activities() -> Select<Entity> {
|
||||
pub fn find_addressed() -> Select<Entity> {
|
||||
let mut select = Entity::find()
|
||||
.distinct()
|
||||
.select_only()
|
||||
.join(sea_orm::JoinType::InnerJoin, Relation::Activity.def())
|
||||
.join(sea_orm::JoinType::LeftJoin, crate::model::activity::Relation::Object.def())
|
||||
.order_by(crate::model::activity::Column::Published, Order::Desc);
|
||||
|
||||
for col in crate::model::activity::Column::iter() {
|
||||
select = select.select_column_as(col, format!("{}{}", crate::model::activity::Entity.table_name(), col.to_string()));
|
||||
}
|
||||
|
||||
for col in crate::model::object::Column::iter() {
|
||||
select = select.select_column_as(col, format!("{}{}", crate::model::object::Entity.table_name(), col.to_string()));
|
||||
}
|
||||
|
||||
select
|
||||
}
|
||||
|
||||
pub fn find_objects() -> Select<Entity> {
|
||||
let mut select = Entity::find()
|
||||
.distinct()
|
||||
.select_only()
|
||||
.join(sea_orm::JoinType::InnerJoin, Relation::Object.def())
|
||||
.join(sea_orm::JoinType::LeftJoin, Relation::Object.def())
|
||||
.join(sea_orm::JoinType::LeftJoin, Relation::Activity.def())
|
||||
.order_by(crate::model::object::Column::Published, Order::Desc);
|
||||
.filter(
|
||||
// TODO ghetto double inner join because i want to filter out tombstones
|
||||
Condition::any()
|
||||
.add(crate::model::activity::Column::Id.is_not_null())
|
||||
.add(crate::model::object::Column::Id.is_not_null())
|
||||
)
|
||||
.order_by(Column::Published, Order::Desc);
|
||||
|
||||
for col in crate::model::object::Column::iter() {
|
||||
select = select.select_column_as(col, format!("{}{}", crate::model::object::Entity.table_name(), col.to_string()));
|
||||
|
|
|
@ -3,6 +3,8 @@ use sea_orm::{entity::prelude::*, Set};
|
|||
|
||||
use crate::routes::activitypub::jsonld::LD;
|
||||
|
||||
use super::addressing::Event;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "attachments")]
|
||||
pub struct Model {
|
||||
|
@ -59,3 +61,52 @@ impl Related<super::object::Entity> for Entity {
|
|||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
|
||||
#[axum::async_trait]
|
||||
pub trait BatchFillable {
|
||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr>;
|
||||
}
|
||||
|
||||
#[axum::async_trait]
|
||||
impl BatchFillable for &[Event] {
|
||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
||||
let objects : Vec<crate::model::object::Model> = self
|
||||
.iter()
|
||||
.filter_map(|x| match x {
|
||||
Event::Tombstone => None,
|
||||
Event::Activity(_) => None,
|
||||
Event::StrayObject(x) => Some(x.clone()),
|
||||
Event::DeepActivity { activity: _, object } => Some(object.clone()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let attachments = objects.load_many(Entity, db).await?;
|
||||
|
||||
let mut out : std::collections::BTreeMap<String, Vec<Model>> = std::collections::BTreeMap::new();
|
||||
for attach in attachments.into_iter().flatten() {
|
||||
if out.contains_key(&attach.object) {
|
||||
out.get_mut(&attach.object).expect("contains but get failed?").push(attach);
|
||||
} else {
|
||||
out.insert(attach.object.clone(), vec![attach]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
#[axum::async_trait]
|
||||
impl BatchFillable for Vec<Event> {
|
||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
||||
self.as_slice().load_attachments_batch(db).await
|
||||
}
|
||||
}
|
||||
|
||||
#[axum::async_trait]
|
||||
impl BatchFillable for Event {
|
||||
async fn load_attachments_batch(&self, db: &DatabaseConnection) -> Result<std::collections::BTreeMap<String, Vec<Model>>, DbErr> {
|
||||
let x = vec![self.clone()]; // TODO wasteful clone and vec![] but ehhh convenient
|
||||
x.load_attachments_batch(db).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use axum::extract::{Path, Query, State};
|
||||
use sea_orm::{ColumnTrait, QueryFilter};
|
||||
use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
||||
use crate::{errors::UpubError, model::{self, addressing::Event, attachment::BatchFillable}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
||||
|
||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
||||
|
||||
|
@ -19,17 +19,17 @@ pub async fn view(
|
|||
ctx.fetch_activity(&aid).await?;
|
||||
}
|
||||
|
||||
match model::addressing::Entity::find_activities()
|
||||
let row = model::addressing::Entity::find_addressed()
|
||||
.filter(model::activity::Column::Id.eq(&aid))
|
||||
.filter(auth.filter_condition())
|
||||
.into_model::<EmbeddedActivity>()
|
||||
.into_model::<Event>()
|
||||
.one(ctx.db())
|
||||
.await?
|
||||
{
|
||||
Some(activity) => Ok(JsonLD(
|
||||
activity.ap_filled(ctx.db()).await?.ld_context()
|
||||
)),
|
||||
None => Err(UpubError::not_found()),
|
||||
}
|
||||
.ok_or_else(UpubError::not_found)?;
|
||||
|
||||
let mut attachments = row.load_attachments_batch(ctx.db()).await?;
|
||||
let attach = attachments.remove(row.id());
|
||||
|
||||
Ok(JsonLD(row.ap(attach).ld_context()))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use axum::extract::{Path, Query, State};
|
||||
use sea_orm::{ColumnTrait, PaginatorTrait, QueryFilter, QuerySelect};
|
||||
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
||||
|
||||
use crate::{model::{self, addressing::WrappedObject}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
use crate::{model, routes::activitypub::{JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
|
||||
pub async fn get(
|
||||
State(ctx): State<Context>,
|
||||
|
@ -14,13 +14,13 @@ pub async fn get(
|
|||
url!(ctx, "/context/{id}")
|
||||
};
|
||||
|
||||
let count = model::addressing::Entity::find_objects()
|
||||
let count = model::addressing::Entity::find_addressed()
|
||||
.filter(auth.filter_condition())
|
||||
.filter(model::object::Column::Context.eq(context))
|
||||
.count(ctx.db())
|
||||
.await?;
|
||||
|
||||
Ok(JsonLD(ctx.ap_collection(&url!(ctx, "/context/{id}"), Some(count)).ld_context()))
|
||||
crate::server::builders::collection(&url!(ctx, "/context/{id}"), Some(count))
|
||||
}
|
||||
|
||||
pub async fn page(
|
||||
|
@ -29,9 +29,6 @@ pub async fn page(
|
|||
Query(page): Query<Pagination>,
|
||||
AuthIdentity(auth): AuthIdentity,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
|
||||
let context = if id.starts_with('+') {
|
||||
id.replacen('+', "https://", 1).replace('@', "/")
|
||||
} else if id.starts_with("tag:") {
|
||||
|
@ -40,24 +37,13 @@ pub async fn page(
|
|||
url!(ctx, "/context/{id}") // TODO need a better way to figure out which ones are our contexts
|
||||
};
|
||||
|
||||
let items = model::addressing::Entity::find_objects()
|
||||
.filter(auth.filter_condition())
|
||||
.filter(model::object::Column::Context.eq(context))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<WrappedObject>()
|
||||
.all(ctx.db())
|
||||
.await?;
|
||||
|
||||
let mut out = Vec::new();
|
||||
for item in items {
|
||||
out.push(item.ap_filled(ctx.db()).await?);
|
||||
}
|
||||
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/context/{id}/page"),
|
||||
offset, limit, out,
|
||||
).ld_context()
|
||||
))
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/context/{id}/page"),
|
||||
Condition::all()
|
||||
.add(auth.filter_condition())
|
||||
.add(model::object::Column::Context.eq(context)),
|
||||
ctx.db(),
|
||||
page
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use apb::{server::Inbox, Activity, ActivityType};
|
||||
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
||||
use sea_orm::{QueryFilter, QuerySelect};
|
||||
|
||||
use crate::{errors::UpubError, model::{self, addressing::WrappedObject}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
use crate::{errors::UpubError, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
|
||||
use super::{jsonld::LD, JsonLD, Pagination};
|
||||
use super::{JsonLD, Pagination};
|
||||
|
||||
|
||||
pub async fn get(
|
||||
State(ctx): State<Context>,
|
||||
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
Ok(JsonLD(ctx.ap_collection(&url!(ctx, "/inbox"), None).ld_context()))
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
crate::server::builders::collection(&url!(ctx, "/inbox"), None)
|
||||
}
|
||||
|
||||
pub async fn page(
|
||||
|
@ -18,25 +17,13 @@ pub async fn page(
|
|||
AuthIdentity(auth): AuthIdentity,
|
||||
Query(page): Query<Pagination>,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
let objects = model::addressing::Entity::find_objects()
|
||||
.filter(auth.filter_condition())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<WrappedObject>()
|
||||
.all(ctx.db())
|
||||
.await?;
|
||||
let mut out = Vec::new();
|
||||
for object in objects {
|
||||
out.push(object.ap_filled(ctx.db()).await?);
|
||||
}
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/inbox/page"),
|
||||
offset, limit, out,
|
||||
).ld_context()
|
||||
))
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/inbox/page"),
|
||||
auth.filter_condition(),
|
||||
ctx.db(),
|
||||
page
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
macro_rules! pretty_json {
|
||||
|
|
|
@ -4,7 +4,7 @@ use apb::{BaseMut, CollectionMut, ObjectMut};
|
|||
use axum::extract::{Path, Query, State};
|
||||
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::Event}, server::{auth::AuthIdentity, fetcher::Fetcher, Context}};
|
||||
|
||||
use super::{jsonld::LD, JsonLD, TryFetch};
|
||||
|
||||
|
@ -23,17 +23,22 @@ pub async fn view(
|
|||
ctx.fetch_object(&oid).await?;
|
||||
}
|
||||
|
||||
let Some(object) = model::addressing::Entity::find_objects()
|
||||
let item = model::addressing::Entity::find_addressed()
|
||||
.filter(model::object::Column::Id.eq(&oid))
|
||||
.filter(auth.filter_condition())
|
||||
.into_model::<WrappedObject>()
|
||||
.into_model::<Event>()
|
||||
.one(ctx.db())
|
||||
.await?
|
||||
else {
|
||||
return Err(UpubError::not_found());
|
||||
.ok_or_else(UpubError::not_found)?;
|
||||
|
||||
let object = match item {
|
||||
Event::Tombstone => return Err(UpubError::not_found()),
|
||||
Event::Activity(_) => return Err(UpubError::not_found()),
|
||||
Event::StrayObject(x) => x,
|
||||
Event::DeepActivity { activity: _, object } => object,
|
||||
};
|
||||
|
||||
let attachments = object.object.find_related(model::attachment::Entity)
|
||||
let attachments = object.find_related(model::attachment::Entity)
|
||||
.all(ctx.db())
|
||||
.await?
|
||||
.into_iter()
|
||||
|
@ -45,10 +50,10 @@ pub async fn view(
|
|||
.set_id(Some(&crate::url!(ctx, "/objects/{id}/replies")))
|
||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||
.set_first(apb::Node::link(crate::url!(ctx, "/objects/{id}/replies/page")))
|
||||
.set_total_items(Some(object.object.comments as u64));
|
||||
.set_total_items(Some(object.comments as u64));
|
||||
|
||||
Ok(JsonLD(
|
||||
object.object.ap()
|
||||
object.ap()
|
||||
.set_replies(apb::Node::object(replies))
|
||||
.set_attachment(apb::Node::array(attachments))
|
||||
.ld_context()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use axum::extract::{Path, Query, State};
|
||||
use sea_orm::{ColumnTrait, PaginatorTrait, QueryFilter, QuerySelect};
|
||||
use sea_orm::{ColumnTrait, Condition, PaginatorTrait, QueryFilter};
|
||||
|
||||
use crate::{model::{self, addressing::WrappedObject}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
use crate::{model, routes::activitypub::{JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
|
||||
pub async fn get(
|
||||
State(ctx): State<Context>,
|
||||
|
@ -14,13 +14,13 @@ pub async fn get(
|
|||
ctx.oid(id.clone())
|
||||
};
|
||||
|
||||
let count = model::addressing::Entity::find_objects()
|
||||
let count = model::addressing::Entity::find_addressed()
|
||||
.filter(auth.filter_condition())
|
||||
.filter(model::object::Column::InReplyTo.eq(oid))
|
||||
.count(ctx.db())
|
||||
.await?;
|
||||
|
||||
Ok(JsonLD(ctx.ap_collection(&url!(ctx, "/objects/{id}/replies"), Some(count)).ld_context()))
|
||||
crate::server::builders::collection(&url!(ctx, "/objects/{id}/replies"), Some(count))
|
||||
}
|
||||
|
||||
pub async fn page(
|
||||
|
@ -29,33 +29,19 @@ pub async fn page(
|
|||
Query(page): Query<Pagination>,
|
||||
AuthIdentity(auth): AuthIdentity,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
|
||||
let oid = if id.starts_with('+') {
|
||||
format!("https://{}", id.replacen('+', "", 1).replace('@', "/"))
|
||||
} else {
|
||||
ctx.oid(id.clone())
|
||||
};
|
||||
|
||||
let items = model::addressing::Entity::find_objects()
|
||||
.filter(auth.filter_condition())
|
||||
.filter(model::object::Column::InReplyTo.eq(oid))
|
||||
// TODO also limit to only local activities
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<WrappedObject>()
|
||||
.all(ctx.db())
|
||||
.await?;
|
||||
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/objects/{id}/replies/page"),
|
||||
offset, limit,
|
||||
items
|
||||
.into_iter()
|
||||
.map(|x| x.object.ap())
|
||||
.collect()
|
||||
).ld_context()
|
||||
))
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/objects/{id}/replies/page"),
|
||||
Condition::all()
|
||||
.add(auth.filter_condition())
|
||||
.add(model::object::Column::InReplyTo.eq(oid)),
|
||||
ctx.db(),
|
||||
page
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use axum::{extract::{Query, State}, http::StatusCode, Json};
|
||||
use sea_orm::{QueryFilter, QuerySelect};
|
||||
|
||||
use crate::{errors::UpubError, model::{self, addressing::WrappedObject}, routes::activitypub::{jsonld::LD, CreationResult, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
use crate::{errors::UpubError, routes::activitypub::{CreationResult, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url};
|
||||
|
||||
pub async fn get(State(ctx): State<Context>) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
Ok(JsonLD(ctx.ap_collection(&url!(ctx, "/outbox"), None).ld_context()))
|
||||
pub async fn get(State(ctx): State<Context>) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
crate::server::builders::collection(&url!(ctx, "/outbox"), None)
|
||||
}
|
||||
|
||||
pub async fn page(
|
||||
|
@ -12,29 +11,13 @@ pub async fn page(
|
|||
Query(page): Query<Pagination>,
|
||||
AuthIdentity(auth): AuthIdentity,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
|
||||
let items = model::addressing::Entity::find_objects()
|
||||
.filter(auth.filter_condition())
|
||||
// TODO also limit to only local activities
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<WrappedObject>()
|
||||
.all(ctx.db()).await?;
|
||||
|
||||
let mut out = Vec::new();
|
||||
for item in items {
|
||||
out.push(item.ap_filled(ctx.db()).await?);
|
||||
}
|
||||
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/outbox/page"),
|
||||
offset, limit,
|
||||
out,
|
||||
).ld_context()
|
||||
))
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/outbox/page"),
|
||||
auth.filter_condition(), // TODO filter local only stuff
|
||||
ctx.db(),
|
||||
page,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn post(
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use axum::{extract::{Path, Query, State}, http::StatusCode};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QuerySelect, SelectColumns};
|
||||
|
||||
use crate::{routes::activitypub::{jsonld::LD, JsonLD, Pagination}, model, server::Context, url};
|
||||
use crate::{routes::activitypub::{JsonLD, Pagination}, model, server::Context, url};
|
||||
|
||||
use model::relation::Column::{Following, Follower};
|
||||
|
||||
pub async fn get<const OUTGOING: bool>(
|
||||
State(ctx): State<Context>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let follow___ = if OUTGOING { "following" } else { "followers" };
|
||||
let count = model::relation::Entity::find()
|
||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))
|
||||
|
@ -16,44 +16,32 @@ pub async fn get<const OUTGOING: bool>(
|
|||
tracing::error!("failed counting {follow___} for {id}: {e}");
|
||||
0
|
||||
});
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection(
|
||||
&url!(ctx, "/users/{id}/{follow___}"),
|
||||
Some(count)
|
||||
).ld_context()
|
||||
))
|
||||
|
||||
crate::server::builders::collection(&url!(ctx, "/users/{id}/{follow___}"), Some(count))
|
||||
}
|
||||
|
||||
pub async fn page<const OUTGOING: bool>(
|
||||
State(ctx): State<Context>,
|
||||
Path(id): Path<String>,
|
||||
Query(page): Query<Pagination>,
|
||||
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let follow___ = if OUTGOING { "following" } else { "followers" };
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
match model::relation::Entity::find()
|
||||
|
||||
let following = model::relation::Entity::find()
|
||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))
|
||||
.select_only()
|
||||
.select_column(if OUTGOING { Following } else { Follower })
|
||||
.limit(limit)
|
||||
.offset(page.offset.unwrap_or(0))
|
||||
.into_tuple::<String>()
|
||||
.all(ctx.db()).await
|
||||
{
|
||||
Err(e) => {
|
||||
tracing::error!("error queriying {follow___} for {id}: {e}");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
},
|
||||
Ok(following) => {
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/users/{id}/{follow___}/page"),
|
||||
offset,
|
||||
limit,
|
||||
following.into_iter().map(serde_json::Value::String).collect()
|
||||
).ld_context()
|
||||
))
|
||||
},
|
||||
}
|
||||
.all(ctx.db())
|
||||
.await?;
|
||||
|
||||
crate::server::builders::collection_page(
|
||||
&url!(ctx, "/users/{id}/{follow___}/page"),
|
||||
offset, limit,
|
||||
following.into_iter().map(serde_json::Value::String).collect()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||
|
||||
use sea_orm::{ColumnTrait, QueryFilter, QuerySelect};
|
||||
use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
use sea_orm::{ColumnTrait, Condition};
|
||||
use crate::{errors::UpubError, model, routes::activitypub::{JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
|
||||
pub async fn get(
|
||||
State(ctx): State<Context>,
|
||||
Path(id): Path<String>,
|
||||
AuthIdentity(auth): AuthIdentity,
|
||||
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
match auth {
|
||||
Identity::Anonymous => Err(StatusCode::FORBIDDEN),
|
||||
Identity::Remote(_) => Err(StatusCode::FORBIDDEN),
|
||||
Identity::Anonymous => Err(StatusCode::FORBIDDEN.into()),
|
||||
Identity::Remote(_) => Err(StatusCode::FORBIDDEN.into()),
|
||||
Identity::Local(user) => if ctx.uid(id.clone()) == user {
|
||||
Ok(JsonLD(ctx.ap_collection(&url!(ctx, "/users/{id}/inbox"), None).ld_context()))
|
||||
crate::server::builders::collection(&url!(ctx, "/users/{id}/inbox"), None)
|
||||
} else {
|
||||
Err(StatusCode::FORBIDDEN)
|
||||
Err(StatusCode::FORBIDDEN.into())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -25,32 +25,21 @@ pub async fn page(
|
|||
AuthIdentity(auth): AuthIdentity,
|
||||
Query(page): Query<Pagination>,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let Identity::Local(uid) = auth else {
|
||||
let Identity::Local(uid) = &auth else {
|
||||
// local inbox is only for local users
|
||||
return Err(UpubError::forbidden());
|
||||
};
|
||||
if uid != ctx.uid(id.clone()) {
|
||||
if uid != &ctx.uid(id.clone()) {
|
||||
return Err(UpubError::forbidden());
|
||||
}
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
let activities = model::addressing::Entity::find_activities()
|
||||
.filter(model::addressing::Column::Actor.eq(&uid))
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.into_model::<EmbeddedActivity>()
|
||||
.all(ctx.db())
|
||||
.await?;
|
||||
let mut out = Vec::new();
|
||||
for activity in activities {
|
||||
out.push(activity.ap_filled(ctx.db()).await?);
|
||||
}
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/users/{id}/inbox/page"),
|
||||
offset, limit, out,
|
||||
).ld_context()
|
||||
))
|
||||
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/users/{id}/inbox/page"),
|
||||
Condition::all().add(model::addressing::Column::Actor.eq(uid)),
|
||||
ctx.db(),
|
||||
page,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn post(
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
use axum::{extract::{Path, Query, State}, http::StatusCode, Json};
|
||||
use sea_orm::{ColumnTrait, QueryFilter, QuerySelect};
|
||||
use sea_orm::{ColumnTrait, Condition};
|
||||
|
||||
use apb::{server::Outbox, AcceptType, ActivityType, Base, BaseType, ObjectType, RejectType};
|
||||
use crate::{errors::UpubError, model::{self, addressing::EmbeddedActivity}, routes::activitypub::{jsonld::LD, CreationResult, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
use crate::{errors::UpubError, model, routes::activitypub::{CreationResult, JsonLD, Pagination}, server::{auth::{AuthIdentity, Identity}, Context}, url};
|
||||
|
||||
pub async fn get(
|
||||
State(ctx): State<Context>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<JsonLD<serde_json::Value>, StatusCode> {
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection(&url!(ctx, "/users/{id}/outbox"), None).ld_context()
|
||||
))
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
crate::server::builders::collection(&url!(ctx, "/users/{id}/outbox"), None)
|
||||
}
|
||||
|
||||
pub async fn page(
|
||||
|
@ -24,28 +22,19 @@ pub async fn page(
|
|||
} else {
|
||||
ctx.uid(id.clone())
|
||||
};
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
|
||||
let activities = model::addressing::Entity::find_activities()
|
||||
.filter(model::activity::Column::Actor.eq(&uid))
|
||||
.filter(auth.filter_condition())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<EmbeddedActivity>()
|
||||
.all(ctx.db()).await?;
|
||||
|
||||
let mut out = Vec::new();
|
||||
for activity in activities {
|
||||
out.push(activity.ap_filled(ctx.db()).await?);
|
||||
}
|
||||
|
||||
Ok(JsonLD(
|
||||
ctx.ap_collection_page(
|
||||
&url!(ctx, "/users/{id}/outbox/page"),
|
||||
offset, limit, out,
|
||||
).ld_context()
|
||||
))
|
||||
crate::server::builders::paginate(
|
||||
url!(ctx, "/users/{id}/outbox/page"),
|
||||
Condition::all()
|
||||
.add(auth.filter_condition())
|
||||
.add(
|
||||
Condition::any()
|
||||
.add(model::activity::Column::Actor.eq(&uid))
|
||||
.add(model::object::Column::AttributedTo.eq(&uid))
|
||||
),
|
||||
ctx.db(),
|
||||
page,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn post(
|
||||
|
|
|
@ -70,7 +70,7 @@ pub async fn statuses(
|
|||
Query(_query): Query<StatusesQuery>,
|
||||
) -> Result<Json<Vec<Status>>, StatusCode> {
|
||||
let uid = ctx.uid(id);
|
||||
model::addressing::Entity::find_activities()
|
||||
model::addressing::Entity::find_addressed()
|
||||
.filter(model::activity::Column::Actor.eq(uid))
|
||||
.filter(auth.filter_condition());
|
||||
|
||||
|
|
64
src/server/builders.rs
Normal file
64
src/server/builders.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use apb::{BaseMut, CollectionMut, CollectionPageMut};
|
||||
use sea_orm::{Condition, DatabaseConnection, QueryFilter, QuerySelect};
|
||||
|
||||
use crate::{model::{addressing::Event, attachment::BatchFillable}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}};
|
||||
|
||||
pub async fn paginate(
|
||||
id: String,
|
||||
filter: Condition,
|
||||
db: &DatabaseConnection,
|
||||
page: Pagination,
|
||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let limit = page.batch.unwrap_or(20).min(50);
|
||||
let offset = page.offset.unwrap_or(0);
|
||||
|
||||
let items = crate::model::addressing::Entity::find_addressed()
|
||||
.filter(filter)
|
||||
// TODO also limit to only local activities
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.into_model::<Event>()
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
let mut attachments = items.load_attachments_batch(db).await?;
|
||||
|
||||
let items : Vec<serde_json::Value> = items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let attach = attachments.remove(item.id());
|
||||
item.ap(attach)
|
||||
})
|
||||
.collect();
|
||||
|
||||
collection_page(&id, offset, limit, items)
|
||||
}
|
||||
|
||||
pub fn collection_page(id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
let next = if items.len() < limit as usize {
|
||||
apb::Node::Empty
|
||||
} else {
|
||||
apb::Node::link(format!("{id}?offset={}", offset+limit))
|
||||
};
|
||||
Ok(JsonLD(
|
||||
serde_json::Value::new_object()
|
||||
.set_id(Some(&format!("{id}?offset={offset}")))
|
||||
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
|
||||
.set_part_of(apb::Node::link(id.replace("/page", "")))
|
||||
.set_ordered_items(apb::Node::array(items))
|
||||
.set_next(next)
|
||||
.ld_context()
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
pub fn collection(id: &str, total_items: Option<u64>) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||
Ok(JsonLD(
|
||||
serde_json::Value::new_object()
|
||||
.set_id(Some(id))
|
||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||
.set_first(apb::Node::link(format!("{id}/page")))
|
||||
.set_total_items(total_items)
|
||||
.ld_context()
|
||||
))
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use apb::{BaseMut, CollectionMut, CollectionPageMut};
|
||||
use openssl::rsa::Rsa;
|
||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||
|
||||
use crate::{model, routes::activitypub::jsonld::LD, server::fetcher::Fetcher};
|
||||
use crate::{model, server::fetcher::Fetcher};
|
||||
|
||||
use super::dispatcher::Dispatcher;
|
||||
|
||||
|
@ -221,30 +220,6 @@ impl Context {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO should probs not be here
|
||||
pub fn ap_collection(&self, id: &str, total_items: Option<u64>) -> serde_json::Value {
|
||||
serde_json::Value::new_object()
|
||||
.set_id(Some(id))
|
||||
.set_collection_type(Some(apb::CollectionType::OrderedCollection))
|
||||
.set_first(apb::Node::link(format!("{id}/page")))
|
||||
.set_total_items(total_items)
|
||||
}
|
||||
|
||||
// TODO should probs not be here
|
||||
pub fn ap_collection_page(&self, id: &str, offset: u64, limit: u64, items: Vec<serde_json::Value>) -> serde_json::Value {
|
||||
serde_json::Value::new_object()
|
||||
.set_id(Some(&format!("{id}?offset={offset}")))
|
||||
.set_collection_type(Some(apb::CollectionType::OrderedCollectionPage))
|
||||
.set_part_of(apb::Node::link(id.replace("/page", "")))
|
||||
.set_ordered_items(apb::Node::array(items))
|
||||
.set_next(
|
||||
if items.len() < limit as usize {
|
||||
apb::Node::Empty
|
||||
} else {
|
||||
apb::Node::link(format!("{id}?offset={}", offset+limit))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn dispatch(&self, uid: &str, activity_targets: Vec<String>, aid: &str, oid: Option<&str>) -> crate::Result<()> {
|
||||
let addressed = self.expand_addressing(activity_targets).await?;
|
||||
|
|
|
@ -4,5 +4,6 @@ pub mod fetcher;
|
|||
pub mod inbox;
|
||||
pub mod outbox;
|
||||
pub mod auth;
|
||||
pub mod builders;
|
||||
|
||||
pub use context::Context;
|
||||
|
|
Loading…
Reference in a new issue