feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready #1
Reference in New Issue
Block a user
Delete Branch "v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is
Complete ground-up rewrite of the thoughts backend + frontend wiring. Same domain, new architecture: hexagonal (ports & adapters), raw sqlx (no ORM), NATS for async events, full ActivityPub federation, two binaries (
thoughtsAPI +thoughts-worker).Replaces the v1 Rust backend (SeaORM, no federation, tightly coupled persistence).
Architecture
What's implemented
Core API
ThoughtResponsewith author, counts, viewer flagsActivityPub federation (Mastodon-compatible)
/users/{username})ThoughtCreated→ Create(Note),ThoughtDeleted→ Delete,ThoughtUpdated→ Update(Note),BoostAdded→ Announce (deterministic ID),BoostRemoved→ Undo(Announce) — delivered to accepted follower inboxes with deduplication (shared inbox), domain/actor blocking respected, 3x retry with exponential backoffEvents (NATS)
OutboundFederationPort+FederationEventService— AP fan-outNotificationEventService— in-app notificationsTransportInfrastructure
/docs(Swagger UI) +/scalarHOST,PORT,CORS_ORIGINS,ALLOW_REGISTRATION,RATE_LIMIT(tower-governor, per-minute, 429 on breach)deploy.sh(build amd64 + push to registry)Architecture cleanup (pre-merge)
Three hexagonal boundary violations fixed before merge:
from_db_str/as_stronFollowState,Visibility,NotificationTypemoved to private helpers in each postgres adapterfollow_actoruse case — local/remote routing decision lifted from presentation handler to application layer; tested with two unit testspublic_key/private_keyremoved fromUserdomain model — HTTP-signature key material belongs in the federation adapter only; also fixes a latent bug whereUserRepository::save()would overwrite keys managed by the federation adapterTest strategy
cargo test -p domain -p application …): 68 tests, zero DB, useTestStorein-memory fakes — run in CI without any servicecargo test -p postgres …): test actual SQL against a real Postgres instanceDeployment
Two-domain Traefik setup (nginx proxy removed):
thoughts.gabrielkaszewski.dev→ frontend (Next.js, port 3000)api.thoughts.gabrielkaszewski.dev→ backend (Axum, port 8000)NATS via external
shared-servicesnetwork (nats://k_nats:4222), same as movies-diary.Key environment variables:
Data migration from v1: one-shot SQL script (kept off-repo — contains user data). Handles all table renames, column renames (
author_id→user_id,reply_to_id→in_reply_to_id), visibility value mapping (friends_only→followers), and required new fields (local=true,state='accepted').Breaking changes from v1
AUTH_SECRET→JWT_SECRET/users/{username}/follower-listand/following-list(AP owns the original paths)Known gaps
DELETE /users/{username}/followreturns 400 for remote handles. The underlyingActivityPubService::unfollow()method exists and is correct; it just isn't wired to the handler yet.*) — setCORS_ORIGINSin productionfeat: v2 backend rewrite — hexagonal architecture + full REST APIto feat: v2 backend rewrite — hexagonal architecture, full-text search, NATS events, ActivityPub federationfeat: v2 backend rewrite — hexagonal architecture, full-text search, NATS events, ActivityPub federationto feat: v2 backend rewrite — hexagonal architecture, ActivityPub federation, NATS eventsfeat: v2 backend rewrite — hexagonal architecture, ActivityPub federation, NATS eventsto feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready