diff --git a/src/routes/activitypub/mod.rs b/src/routes/activitypub/mod.rs index 57ed8f7c..dd4140fe 100644 --- a/src/routes/activitypub/mod.rs +++ b/src/routes/activitypub/mod.rs @@ -58,12 +58,12 @@ impl ActivityPubRouter for Router { .route("/context/:id/page", get(ap::context::page)) // specific object routes .route("/objects/:id", get(ap::object::view)) + .route("/objects/:id/replies", get(ap::object::replies::get)) + .route("/objects/:id/replies/page", get(ap::object::replies::page)) //.route("/objects/:id/likes", get(ap::object::likes::get)) //.route("/objects/:id/likes/page", get(ap::object::likes::page)) //.route("/objects/:id/announces", get(ap::object::announces::get)) //.route("/objects/:id/announces/page", get(ap::object::announces::page)) - //.route("/objects/:id/replies", get(ap::object::replies::get)) - //.route("/objects/:id/replies/page", get(ap::object::replies::page)) } } diff --git a/src/routes/activitypub/object/mod.rs b/src/routes/activitypub/object/mod.rs new file mode 100644 index 00000000..9477b7c1 --- /dev/null +++ b/src/routes/activitypub/object/mod.rs @@ -0,0 +1,36 @@ +pub mod replies; + +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 super::{jsonld::LD, JsonLD, TryFetch}; + +pub async fn view( + State(ctx): State, + Path(id): Path, + AuthIdentity(auth): AuthIdentity, + Query(query): Query, +) -> crate::Result> { + let oid = if id.starts_with('+') { + format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) + } else { + ctx.oid(id.clone()) + }; + match model::addressing::Entity::find_activities() + .filter(model::object::Column::Id.eq(&oid)) + .filter(auth.filter_condition()) + .into_model::() + .one(ctx.db()) + .await? + { + Some(EmbeddedActivity { activity: _, object: Some(object) }) => Ok(JsonLD(object.ap().ld_context())), + Some(EmbeddedActivity { activity: _, object: None }) => Err(UpubError::not_found()), + None => if auth.is_local() && query.fetch && !ctx.is_local(&oid) { + Ok(JsonLD(ctx.fetch_object(&oid).await?.ap().ld_context())) + } else { + Err(UpubError::not_found()) + }, + } +} diff --git a/src/routes/activitypub/object/replies.rs b/src/routes/activitypub/object/replies.rs new file mode 100644 index 00000000..c506df9d --- /dev/null +++ b/src/routes/activitypub/object/replies.rs @@ -0,0 +1,62 @@ +use axum::extract::{Path, Query, State}; +use sea_orm::{ColumnTrait, Order, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect}; + +use crate::{model::{self, addressing::EmbeddedActivity}, routes::activitypub::{jsonld::LD, JsonLD, Pagination}, server::{auth::AuthIdentity, Context}, url}; + +pub async fn get( + State(ctx): State, + Path(id): Path, + AuthIdentity(auth): AuthIdentity, +) -> crate::Result> { + let oid = if id.starts_with('+') { + format!("https://{}", id.replacen('+', "", 1).replace('@', "/")) + } else { + ctx.oid(id.clone()) + }; + + let count = model::addressing::Entity::find_objects() + .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())) +} + +pub async fn page( + State(ctx): State, + Path(id): Path, + Query(page): Query, + AuthIdentity(auth): AuthIdentity, +) -> crate::Result> { + 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 + .order_by(model::addressing::Column::Published, Order::Desc) + .limit(limit) + .offset(offset) + .into_model::() + .all(ctx.db()) + .await?; + + Ok(JsonLD( + ctx.ap_collection_page( + &url!(ctx, "/objects/{id}/replies"), + offset, limit, + items + .into_iter() + .filter_map(|x| Some(x.object?.ap())) + .collect() + ).ld_context() + )) +}