feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready #1

Merged
GKaszewski merged 334 commits from v2 into master 2026-05-16 09:42:43 +00:00
5 changed files with 62 additions and 7 deletions
Showing only changes of commit e0a27c99a4 - Show all commits

View File

@@ -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"

View File

@@ -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,
}
}

View File

@@ -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());

View File

@@ -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<AppState> {
Router::new()
pub fn router(fed_config: &ApFederationConfig) -> Router<AppState> {
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<AppState> {
"/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()))
}

View File

@@ -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<dyn AuthService>,
pub hasher: Arc<dyn PasswordHasher>,
pub events: Arc<dyn EventPublisher>,
pub fed_config: ApFederationConfig,
}