Skip to content

About

todo

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Rootedz

Rootedz is a community platform built around a simple hierarchy:

Root
└── Branch
    └── Leaf
  • Root is a community, category, or public topic.
  • Branch is a post published inside a Root.
  • Leaf is a comment attached to a Branch.

The product is designed as a focused Reddit-style forum with a light green visual identity, practical moderation controls, and a production-ready deployment stack.

Features

  • Username-based sign in.
  • Case-insensitive usernames and email addresses.
  • Email verification flow.
  • Automatic country detection from trusted edge headers during registration.
  • Root, Branch, and Leaf CRUD operations.
  • Branch likes with persistent like state and counters.
  • Helpful marks on comments.
  • Profile statistics for posts, comments, likes, and helpful marks.
  • JWT authentication with browser session storage.
  • Pagination for feeds and comment lists.
  • PostgreSQL persistence with versioned SQL migrations.
  • RabbitMQ outbox publishing, retries, and dead-letter queues.
  • Nginx security headers, request limits, and static frontend serving.
  • Docker Compose environments for local and production use.
  • Cloudflare Tunnel deployment without exposing application ports publicly.
  • Prometheus metrics and Grafana dashboards.

Technology stack

Backend

  • Java 17
  • Spring Boot 4
  • Spring Web, Spring Data JPA, and Hibernate
  • Spring Security with JWT
  • PostgreSQL
  • RabbitMQ
  • Gradle

Frontend

  • React
  • Vite
  • React Router
  • TanStack Query
  • Vitest
  • CSS
  • IBM Plex typography

Operations

  • Docker and Docker Compose
  • Nginx
  • Cloudflare Tunnel
  • Prometheus
  • Grafana

Repository layout

Rooted_API/
├── src/main/java/             Backend source code
├── src/main/resources/        Spring configuration and migrations
├── frontend/                  React frontend
├── deploy/
│   ├── postgres/               PostgreSQL migrations and helpers
│   ├── monitoring/             Prometheus and Grafana configuration
│   ├── nginx/                  Production reverse proxy configuration
│   └── secrets/                Local-only certificates and secrets
├── docker-compose.yml          Local application dependencies
├── compose.production.yml      Production stack
├── compose.cloudflare.yml      Cloudflare Tunnel service
├── Dockerfile                  Backend image
├── .env.example                Environment variable template
└── gradlew.bat                 Gradle wrapper for Windows

Requirements

  • JDK 17 or newer
  • Docker Desktop with Docker Compose
  • Node.js 20 or newer
  • npm
  • A PostgreSQL database
  • RabbitMQ for local development, unless it is already available elsewhere

Local development

1. Configure environment variables

Copy the template and fill in local values:

Copy-Item .env.example .env

Never commit .env, production credentials, JWT secrets, database certificates, or tunnel tokens.

2. Start RabbitMQ

docker compose up -d rabbitmq

If you already have RabbitMQ and PostgreSQL running, configure their connection values in .env and skip this step.

3. Start the backend

.\gradlew.bat bootRun

The backend listens on http://localhost:8080.

Useful endpoints:

  • API base: http://localhost:8080/api
  • Health: http://localhost:8080/actuator/health
  • Metrics: http://localhost:8080/actuator/prometheus

4. Start the frontend

Set-Location frontend
npm.cmd install
npm.cmd run dev

The Vite development server listens on http://localhost:5173.

For a production-like frontend build:

npm.cmd run build
npm.cmd run preview

Authentication

Registration and login use a username, not an email address.

Usernames and email addresses are normalized to lower case for lookup, so these values are treated as equivalent:

RootedUser
rooteduser
ROOTEDUSER

Example registration request:

POST /auth/register
Content-Type: application/json
{
  "username": "rooteduser",
  "email": "person@example.com",
  "password": "change-me"
}

Example login request:

POST /auth/login
Content-Type: application/json
{
  "username": "rooteduser",
  "password": "change-me"
}

The frontend stores the authenticated session in browser session storage. Clear the session by signing out or removing the application session from browser storage.

Country detection

The registration form does not ask the user to enter a country code. The backend resolves the country from a trusted reverse-proxy header.

Supported headers include:

  • CF-IPCountry for Cloudflare
  • CloudFront-Viewer-Country for Amazon CloudFront
  • X-Vercel-IP-Country for Vercel
  • A custom header configured with COUNTRY_HEADER_NAME

Production configuration should enable trusted headers only behind a proxy that controls and overwrites those headers:

TRUST_COUNTRY_HEADERS=true
COUNTRY_HEADER_NAME=CF-IPCountry

If no trusted country value is available, the backend stores the fallback code ZZ.

Social interactions

Branch likes

Like a Branch:

POST /branch/{branchId}/like
Authorization: Bearer <token>

Remove a like:

DELETE /branch/{branchId}/like
Authorization: Bearer <token>

The API returns the current like state and count. The frontend updates the Branch card immediately and then synchronizes with the server.

Helpful marks

Mark a Leaf as helpful:

POST /leaves/like/{leafId}
Authorization: Bearer <token>

Remove a helpful mark:

DELETE /leaves/unlike/{leafId}
Authorization: Bearer <token>

Helpful marks are persisted per user. Profile counters and comment lists use the stored values instead of decorative placeholders.

Pagination and counters

Feed and comment endpoints expose paginated results. A typical response contains:

{
  "content": [],
  "page": 0,
  "size": 20,
  "totalElements": 0,
  "totalPages": 0,
  "last": true
}

Important counters include:

  • commentsCount on Branch responses
  • helpfulMarks on Leaf responses
  • Branch like count and the current user's like state
  • Profile totals for created Roots, Branches, Leaves, likes, and helpful marks

Database migrations

Versioned PostgreSQL scripts are stored in deploy/postgres/:

001_case_insensitive_users.sql
002_branch_likes.sql
003_leaf_helpful_marks.sql
004_profile_statistics.sql

Run them in numeric order against the target database. Back up the database before applying migrations in production. The application is safe to restart after a completed migration, but each migration should be applied only once.

Messaging and outbox processing

Domain events are published through RabbitMQ using an outbox workflow. This keeps database writes and event publication recoverable when the broker is temporarily unavailable.

Relevant environment variables include:

RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USERNAME=rooted
RABBITMQ_PASSWORD=rooted
RABBITMQ_RETRY_MAX_ATTEMPTS=5
RABBITMQ_RETRY_INITIAL_DELAY_MS=1000

Failed messages are retried and eventually moved to dead-letter queues. Inspect the RabbitMQ management UI when diagnosing delivery failures.

Production deployment

The production deployment uses three Compose files:

docker compose -f compose.production.yml -f compose.cloudflare.yml up -d --build

The stack contains:

  • Backend API
  • React frontend served by Nginx
  • RabbitMQ
  • Cloudflare Tunnel
  • Prometheus
  • Grafana

Only the monitoring services bind to loopback ports by default. The public application traffic goes through Cloudflare Tunnel, so backend and frontend containers do not need public host port mappings.

Production environment

Create a protected environment file on the server:

cp .env.example deploy/production.env
chmod 600 deploy/production.env

Set production values, including:

APP_PUBLIC_BASE_URL=https://rootedz.eu
APP_FRONTEND_BASE_URL=https://rootedz.eu
CORS_ALLOWED_ORIGINS=https://rootedz.eu,https://www.rootedz.eu
TRUST_COUNTRY_HEADERS=true
COUNTRY_HEADER_NAME=CF-IPCountry

Do not store the Cloudflare token in the repository. Pass it through the server environment or an ignored production environment file.

Cloudflare Tunnel routes

Configure the tunnel to route both hostnames to the frontend service:

rootedz.eu     -> http://frontend:80
www.rootedz.eu -> http://frontend:80

The frontend Nginx configuration proxies API requests to the backend container over the private Compose network.

PostgreSQL TLS

For a managed PostgreSQL provider that requires certificate verification, mount the provider CA certificate as a Docker secret and use a JDBC query similar to:

DB_JDBC_QUERY=sslmode=verify-full&sslrootcert=/run/secrets/postgres-ca.pem
DB_CA_CERT_FILE=./deploy/secrets/postgres-ca.pem

Keep the CA certificate outside Git unless it is explicitly safe to publish.

Monitoring

Prometheus is available on loopback port 9090 and Grafana on loopback port 3000. For remote access, use an SSH tunnel instead of exposing these ports publicly:

ssh -L 3000:127.0.0.1:3000 -L 9090:127.0.0.1:9090 user@server

Then open:

  • Grafana: http://localhost:3000
  • Prometheus: http://localhost:9090

Email DNS

Email delivery requires valid DNS records for the mail provider, including MX, SPF, DKIM, DMARC, and any provider-specific autodiscovery or MTA-STS records. Keep mail DNS records separate from the web application tunnel records.

Frontend routes

The frontend includes the following main views:

  • / for the Root feed
  • /r/:rootTitle for a Root and its Branches
  • /b/:branchId for a Branch and its Leaves
  • /u/:username for user statistics and activity
  • /login and /register for authentication
  • /auth/verify for email verification
  • /roots/new, /r/:rootTitle/new, and /settings for protected actions

The frontend uses TanStack Query for server state, React Router for navigation, and a shared API client for authentication and request errors.

Testing

Backend tests:

.\gradlew.bat test

Frontend tests:

Set-Location frontend
npm.cmd run test

Frontend lint and build:

npm.cmd run lint
npm.cmd run build

Before a production release, also verify:

  • Registration with a mixed-case username and email.
  • Login using the normalized username.
  • Country detection through the configured proxy header.
  • Creating and deleting a Root, Branch, and Leaf.
  • Liking and unliking a Branch.
  • Adding and removing a helpful mark from a Leaf.
  • Profile counters after each interaction.
  • Cloudflare Tunnel health and both public hostnames.
  • Database backup and migration status.

Security notes

  • Rotate all credentials that have ever been shared in chat, shell history, screenshots, or logs.
  • Use a unique, high-entropy JWT secret in production.
  • Keep .env, deploy/production.env, certificates, tunnel tokens, and database dumps out of Git.
  • Terminate public TLS at Cloudflare and keep the origin reachable only through the tunnel.
  • Do not trust country headers from direct public clients.
  • Restrict Grafana, Prometheus, RabbitMQ, and actuator endpoints to private access.
  • Apply database backups and test restoration regularly.

License

No public license has been declared yet. Add a license file before accepting external contributions or distributing Rootedz under explicit terms.

About

todo

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages