diff --git a/upub/routes/src/activitypub/actor/feed.rs b/upub/routes/src/activitypub/actor/feed.rs new file mode 100644 index 0000000..ff9a607 --- /dev/null +++ b/upub/routes/src/activitypub/actor/feed.rs @@ -0,0 +1,47 @@ +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, + Path(id): Path, + AuthIdentity(auth): AuthIdentity, +) -> crate::ApiResult> { + 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, + Path(id): Path, + AuthIdentity(auth): AuthIdentity, + Query(page): Query, +) -> crate::ApiResult> { + 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_activities( + upub::url!(ctx, "/actors/{id}/feed/page"), + upub::model::addressing::Column::Actor.eq(*internal).into_condition(), + ctx.db(), + page, + auth.my_id(), + false, + ) + .await +} diff --git a/upub/routes/src/activitypub/application.rs b/upub/routes/src/activitypub/application.rs index d22d899..0c90a2a 100644 --- a/upub/routes/src/activitypub/application.rs +++ b/upub/routes/src/activitypub/application.rs @@ -23,6 +23,10 @@ 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)) diff --git a/upub/routes/src/activitypub/feed.rs b/upub/routes/src/activitypub/feed.rs new file mode 100644 index 0000000..afa92b0 --- /dev/null +++ b/upub/routes/src/activitypub/feed.rs @@ -0,0 +1,29 @@ +use axum::extract::{Query, State}; +use upub::Context; + +use crate::{AuthIdentity, builders::JsonLD}; + +use super::Pagination; + + +pub async fn get( + State(ctx): State, +) -> crate::ApiResult> { + crate::builders::collection(&upub::url!(ctx, "/feed"), None) +} + +pub async fn page( + State(ctx): State, + AuthIdentity(auth): AuthIdentity, + Query(page): Query, +) -> crate::ApiResult> { + crate::builders::paginate_objects( + upub::url!(ctx, "/feed/page"), + auth.filter_objects(), + ctx.db(), + page, + auth.my_id(), + false, + ) + .await +} diff --git a/upub/routes/src/activitypub/mod.rs b/upub/routes/src/activitypub/mod.rs index f1ab91a..0bd37f1 100644 --- a/upub/routes/src/activitypub/mod.rs +++ b/upub/routes/src/activitypub/mod.rs @@ -1,6 +1,7 @@ -pub mod user; +pub mod actor; pub mod inbox; pub mod outbox; +pub mod feed; pub mod object; pub mod activity; pub mod application; @@ -24,13 +25,14 @@ impl ActivityPubRouter for Router { .route("/proxy", post(ap::application::proxy_form)) .route("/proxy", get(ap::application::proxy_get)) .route("/proxy/:uri", get(ap::application::proxy_path)) - // TODO shared inboxes and instance stream will come later, just use users *boxes for now .route("/inbox", post(ap::inbox::post)) .route("/inbox", get(ap::inbox::get)) .route("/inbox/page", get(ap::inbox::page)) .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)) @@ -42,17 +44,19 @@ impl ActivityPubRouter for Router { .route("/.well-known/oauth-authorization-server", get(ap::well_known::oauth_authorization_server)) .route("/nodeinfo/:version", get(ap::well_known::nodeinfo)) // actor routes - .route("/actors/:id", get(ap::user::view)) - .route("/actors/:id/inbox", post(ap::user::inbox::post)) - .route("/actors/:id/inbox", get(ap::user::inbox::get)) - .route("/actors/:id/inbox/page", get(ap::user::inbox::page)) - .route("/actors/:id/outbox", post(ap::user::outbox::post)) - .route("/actors/:id/outbox", get(ap::user::outbox::get)) - .route("/actors/:id/outbox/page", get(ap::user::outbox::page)) - .route("/actors/:id/followers", get(ap::user::following::get::)) - .route("/actors/:id/followers/page", get(ap::user::following::page::)) - .route("/actors/:id/following", get(ap::user::following::get::)) - .route("/actors/:id/following/page", get(ap::user::following::page::)) + .route("/actors/:id", get(ap::actor::view)) + .route("/actors/:id/inbox", post(ap::actor::inbox::post)) + .route("/actors/:id/inbox", get(ap::actor::inbox::get)) + .route("/actors/:id/inbox/page", get(ap::actor::inbox::page)) + .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/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::)) + .route("/actors/:id/followers/page", get(ap::actor::following::page::)) + .route("/actors/:id/following", get(ap::actor::following::get::)) + .route("/actors/:id/following/page", get(ap::actor::following::page::)) // activities .route("/activities/:id", get(ap::activity::view)) // specific object routes