Gabriel Kaszewski e2210ba0f0 feat(frontend): performance + DRY + composition overhaul
- Tagged fetch cache: all GET fetches have next.tags for targeted invalidation
- Server Actions (thoughts, social, profile) replace scattered router.refresh()
- Eliminated author profile waterfall — use thought.author directly
- useOptimistic on FollowButton for instant feedback
- ThoughtForm unifies PostThoughtForm and ReplyForm
- EmptyState and LoadingSkeleton shared primitives
- RemoteUserProfile split into ProfileCard + Connections
2026-05-15 23:21:51 +02:00

Thoughts

A self-hosted microblogging server with full ActivityPub federation. Write short posts, follow people on Mastodon and other Fediverse servers, and receive their posts in your feed. Built in Rust with a Next.js frontend.

Features

  • Short-form posts (thoughts) with replies, boosts, and likes
  • Full ActivityPub federation — follow/unfollow remote actors, accept/reject followers, federated content broadcast as Note objects, paginated outbox, NodeInfo discovery, WebFinger, shared inbox, actor profile sync
  • Remote actor discovery — search by @user@instance handle, view full remote profiles (bio, banner, profile fields, posts, followers, following tabs), follow from within the UI
  • Worker-backed remote caches — remote posts and follower/following lists are fetched by the NATS worker and cached locally; profiles populate on first visit and refresh in the background
  • Content negotiation at GET /users/{username} — serves ActivityPub actor JSON or REST profile based on Accept header
  • Federation moderation — per-instance domain blocking, per-user actor blocking with Block activity delivery, delivery filter excludes blocked actors and blocked-domain inboxes
  • Async event fan-out via NATS JetStream — notifications and AP delivery run in a separate worker process; pull consumer with 1-hour TTL caching
  • JWT authentication (Bearer token)
  • OpenAPI documentation at /docs (Swagger UI) and /scalar (Scalar)
  • Full-text search over thoughts and users via PostgreSQL trigram indexes
  • Top friends — pin up to 5 users as highlighted contacts
  • API keys for third-party client access
  • Home feed, public feed, and per-user thought timelines

Architecture

Hexagonal (Ports & Adapters) with Domain-Driven Design:

domain              — pure types and port trait definitions, no external deps
application         — use cases and event processing services (business logic)
api-types           — shared REST API request/response DTOs
presentation        — Axum HTTP router, OpenAPI spec, composition root for the API process
bootstrap           — binary: thoughts (API server)
worker              — binary: thoughts-worker (event consumer — notifications, AP fan-out)
adapters/
  auth                 — JWT issuance and validation, Argon2 password hashing
  postgres             — PostgreSQL repositories for all domain entities
  postgres-search      — PostgreSQL trigram full-text search
  postgres-federation  — PostgreSQL-backed federation repository
  activitypub-base     — core ActivityPub protocol types, ActivityPubService, federation middleware
  activitypub          — project-specific AP wiring (ThoughtsObjectHandler, inbox/outbox)
  nats                 — NATS transport implementing Transport + MessageSource ports
  event-payload        — shared event serialization DTOs
  event-transport      — Transport trait + EventPublisherAdapter / MessageSource + EventConsumerAdapter

Prerequisites

  • Rust stable (1.80+)
  • PostgreSQL 15+
  • NATS (optional — federation and notifications still work without it, events queue in-process)

Environment Variables

Copy .env.example to .env and fill in your values:

DATABASE_URL=postgres://postgres:password@localhost:5432/thoughts
JWT_SECRET=change-me
BASE_URL=http://localhost:3000
NATS_URL=nats://localhost:4222   # optional

See .env.example for all available options.

Run

# API server (runs migrations automatically on startup)
cargo run -p bootstrap

# Event worker — federation fan-out and notifications (separate terminal)
cargo run -p worker

Both processes share the same PostgreSQL database. The worker is optional but required for ActivityPub delivery to remote servers.

Test

# Unit tests — no database required
cargo test -p application

# Full workspace (requires DATABASE_URL pointing to a running PostgreSQL)
cargo test --workspace

The application crate contains unit tests for all event services and use cases backed by in-memory fakes from domain's test-helpers feature. These are the fastest feedback loop for business logic.

API

All REST endpoints are under the root path. Authentication uses Authorization: Bearer <token> obtained from POST /auth/login.

Interactive API documentation is available at runtime:

  • Swagger UIhttp://localhost:8000/docs
  • Scalarhttp://localhost:8000/scalar

Frontend

The Next.js frontend lives in thoughts-frontend/. It requires two environment variables:

NEXT_PUBLIC_API_URL=http://localhost:8000        # client-side requests
NEXT_PUBLIC_SERVER_SIDE_API_URL=http://localhost:8000  # SSR requests
cd thoughts-frontend
bun install
bun run dev   # http://localhost:3000

Docker

The backend image contains both thoughts (API server) and thoughts-worker (event processor). Run them as separate containers:

docker build -t thoughts .

# API server
docker run -p 8000:8000 \
  -e DATABASE_URL=postgres://postgres:password@db:5432/thoughts \
  -e JWT_SECRET=change-me \
  -e BASE_URL=https://yourdomain.example.com \
  -e NATS_URL=nats://nats:4222 \
  thoughts

# Event worker (same image, different entrypoint)
docker run \
  -e DATABASE_URL=postgres://postgres:password@db:5432/thoughts \
  -e BASE_URL=https://yourdomain.example.com \
  -e NATS_URL=nats://nats:4222 \
  --entrypoint ./thoughts-worker \
  thoughts

# Frontend
docker build -t thoughts-frontend \
  --build-arg NEXT_PUBLIC_API_URL=https://api.yourdomain.example.com \
  --build-arg NEXT_PUBLIC_SERVER_SIDE_API_URL=http://thoughts:8000 \
  thoughts-frontend/
docker run -p 3000:3000 thoughts-frontend

See compose.yml for a full local development stack.

License

MIT License. See LICENSE.

Description
Nostalgic social platform with Frutiger Aero style. 128-char posts, custom CSS profiles, and full ActivityPub federation — interoperable with Mastodon, Misskey, and Movies Diary.
https://thoughts.gabrielkaszewski.dev/ Readme MIT 7.8 MiB
Languages
Rust 59.1%
TypeScript 38.9%
CSS 1.4%
Dockerfile 0.3%
Shell 0.1%
Other 0.1%