diff --git a/crates/presentation/Cargo.toml b/crates/presentation/Cargo.toml index dbfd03e..c9611cd 100644 --- a/crates/presentation/Cargo.toml +++ b/crates/presentation/Cargo.toml @@ -30,6 +30,11 @@ dotenvy = { workspace = true } async-trait = { workspace = true } sha2 = "0.10" hex = "0.4" +activitypub = { workspace = true } +activitypub-base = { workspace = true } +postgres-federation = { workspace = true } +url = { workspace = true } +activitypub_federation = "0.7.0-beta.11" [dev-dependencies] http-body-util = "0.1" diff --git a/crates/presentation/src/lib.rs b/crates/presentation/src/lib.rs index afe40c9..3deef45 100644 --- a/crates/presentation/src/lib.rs +++ b/crates/presentation/src/lib.rs @@ -9,6 +9,9 @@ use async_trait::async_trait; use sqlx::PgPool; use domain::{errors::DomainError, events::DomainEvent, ports::EventPublisher}; use postgres_search::PgSearchRepository; +use activitypub_base::{ApFederationConfig, FederationData}; +use activitypub::ThoughtsObjectHandler; +use postgres_federation::{PostgresApUserRepository, PostgresFederationRepository}; use state::AppState; struct NoOpEventPublisher; @@ -35,6 +38,28 @@ pub async fn build_state(pool: PgPool, jwt_secret: String) -> AppState { } }; + let base_url = std::env::var("BASE_URL") + .unwrap_or_else(|_| "http://localhost:3000".into()); + let allow_registration = std::env::var("ALLOW_REGISTRATION") + .map(|v| v == "true") + .unwrap_or(true); + let fed_debug = std::env::var("RUST_ENV") + .map(|v| v != "production") + .unwrap_or(true); + + let fed_data = FederationData::new( + Arc::new(PostgresFederationRepository::new(pool.clone())), + Arc::new(PostgresApUserRepository::new(pool.clone(), base_url.clone())), + Arc::new(ThoughtsObjectHandler::new(pool.clone(), &base_url)), + base_url, + allow_registration, + "thoughts".to_string(), + None, + ); + + let fed_config = ApFederationConfig::new(fed_data, fed_debug).await + .expect("federation config failed"); + AppState { users: Arc::new(postgres::user::PgUserRepository::new(pool.clone())), thoughts: Arc::new(postgres::thought::PgThoughtRepository::new(pool.clone())), @@ -52,5 +77,6 @@ pub async fn build_state(pool: PgPool, jwt_secret: String) -> AppState { auth: Arc::new(auth::JwtAuthService::new(jwt_secret, 86400 * 30)), hasher: Arc::new(auth::Argon2PasswordHasher), events: event_publisher, + fed_config, } } diff --git a/crates/presentation/src/main.rs b/crates/presentation/src/main.rs index a80eff1..b2af0ef 100644 --- a/crates/presentation/src/main.rs +++ b/crates/presentation/src/main.rs @@ -17,7 +17,7 @@ async fn main() { sqlx::migrate!("../adapters/postgres/migrations").run(&pool).await.expect("Migrations failed"); let state = presentation::build_state(pool, jwt_secret).await; - let app = presentation::routes::router() + let app = presentation::routes::router(&state.fed_config) .with_state(state) .layer(CorsLayer::permissive()); diff --git a/crates/presentation/src/routes.rs b/crates/presentation/src/routes.rs index b449bf6..4302dc1 100644 --- a/crates/presentation/src/routes.rs +++ b/crates/presentation/src/routes.rs @@ -2,19 +2,26 @@ use axum::{ routing::{delete, get, patch, post, put}, Router, }; +use activitypub_base::{ + actor_handler::actor_handler, + followers_handler::{followers_handler, following_handler}, + inbox::inbox_handler, + nodeinfo::{nodeinfo_handler, nodeinfo_well_known_handler}, + outbox::outbox_handler, + webfinger::webfinger_handler, + ApFederationConfig, +}; +use activitypub_federation::config::FederationMiddleware; use crate::{handlers::*, state::AppState}; -pub fn router() -> Router { - Router::new() +pub fn router(fed_config: &ApFederationConfig) -> Router { + let api_routes = Router::new() // auth .route("/auth/register", post(auth::post_register)) .route("/auth/login", post(auth::post_login)) // users — static paths before parameterised .route("/users/me", patch(users::patch_profile)) .route("/users/me/top-friends", put(social::put_top_friends)) - .route("/users/{username}", get(users::get_user)) - .route("/users/{username}/following", get(feed::get_following_handler)) - .route("/users/{username}/followers", get(feed::get_followers_handler)) .route("/users/{username}/top-friends", get(social::get_top_friends_handler)) // follows & blocks (use {id} param) .route( @@ -56,5 +63,20 @@ pub fn router() -> Router { "/api-keys", get(api_keys::get_api_keys).post(api_keys::post_api_key), ) - .route("/api-keys/{id}", delete(api_keys::delete_api_key_handler)) + .route("/api-keys/{id}", delete(api_keys::delete_api_key_handler)); + + let ap_routes = Router::new() + .route("/.well-known/webfinger", get(webfinger_handler)) + .route("/.well-known/nodeinfo", get(nodeinfo_well_known_handler)) + .route("/nodeinfo/2.0", get(nodeinfo_handler)) + .route("/users/{username}", get(actor_handler)) + .route("/users/{username}/inbox", post(inbox_handler)) + .route("/users/{username}/outbox", get(outbox_handler)) + .route("/users/{username}/followers", get(followers_handler)) + .route("/users/{username}/following", get(following_handler)); + + Router::new() + .merge(api_routes) + .merge(ap_routes) + .layer(FederationMiddleware::new(fed_config.0.clone())) } diff --git a/crates/presentation/src/state.rs b/crates/presentation/src/state.rs index c582001..92ec928 100644 --- a/crates/presentation/src/state.rs +++ b/crates/presentation/src/state.rs @@ -1,5 +1,6 @@ use std::sync::Arc; use domain::ports::*; +use activitypub_base::ApFederationConfig; #[derive(Clone)] pub struct AppState { @@ -19,4 +20,5 @@ pub struct AppState { pub auth: Arc, pub hasher: Arc, pub events: Arc, + pub fed_config: ApFederationConfig, }