Skip to content

tomassirio/wanderer-backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

545 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Wanderer Logo

Real-time pilgrimage tracking β€” Utrecht to Santiago de Compostela

Java Spring Boot Version Build Status License Coverage

PostgresSQL Docker Kubernetes Architecture

Wanderer is the backend for a real-time pilgrimage tracking platform built for my walk from Utrecht to Santiago de Compostela β€” roughly 48 days and 50 km per day. It lets friends, family, and anyone following along see where I am, read updates, leave comments, and watch achievements unlock as the journey unfolds.

The system is built on a CQRS (Command Query Responsibility Segregation) architecture with three independently deployable Spring Boot services backed by PostgreSQL, connected to a companion frontend through REST APIs and WebSockets.

Table of Contents

πŸ—οΈ Architecture

The backend is a multi-module Maven project split by responsibility:

wanderer-backend/
β”œβ”€β”€ commons/            Shared domain entities, DTOs, mappers, and constants
β”œβ”€β”€ wanderer-auth/      Authentication & authorization          β†’ Port 8083
β”œβ”€β”€ wanderer-command/   Write operations (create, update, delete) β†’ Port 8081
β”œβ”€β”€ wanderer-query/     Read operations (queries & lists)        β†’ Port 8082
β”œβ”€β”€ docs/               Additional guides and documentation
└── docker-compose.yml  Full local stack with two Postgres instances
Layer Responsibility
commons Domain entities, DTOs, MapStruct mappers, ApiConstants, enums, exceptions
wanderer-auth Registration, login, JWT issuance, refresh tokens, password reset, email verification, admin role management
wanderer-command Trip CRUD, trip updates (location posts), comments, reactions, friend requests, follows, admin maintenance, WebSocket event broadcasting
wanderer-query Trip queries, user lookups, comment retrieval, achievement listings, promoted trips, friendship/follow queries, admin statistics

Technology Stack

Category Technology
Language Java 21
Framework Spring Boot 3.5.6
Database PostgreSQL 16 with Liquibase migrations
Security JWT (access + refresh tokens), bcrypt, role-based authorization
Real-Time WebSocket (native Spring WebSocket)
DTO Mapping MapStruct
External APIs Google Maps (polylines, geocoding, weather)
Email SMTP (configurable, Brevo/Sendinblue by default)
Code Style Spotless with Google Java Format (AOSP)
Testing JUnit 5, Cucumber (BDD), JaCoCo (coverage)
Containers Docker via Jib Maven Plugin
Orchestration Kubernetes + Helm
API Docs SpringDoc OpenAPI (Swagger UI)

✨ Features

Trip Tracking

  • Create trips with configurable visibility (Public, Private, Protected) and modality (Simple or Multi-Day)
  • Post location updates with GPS coordinates, altitude, battery level, and messages
  • Automatic polyline generation from location history
  • Reverse geocoding of coordinates into city and country
  • Day-by-day tracking for multi-day trips with start/end day toggling
  • Trip status lifecycle: Created β†’ In Progress β†’ Paused / Resting β†’ Finished

Social

  • Friend requests with accept/decline workflow
  • Bidirectional friendships that unlock access to Protected trips
  • User follows for one-directional social connections
  • Public trip discovery and promoted/featured trips

Comments & Reactions

  • Threaded comments on trips with one level of nesting (replies)
  • Five reaction types on comments: ❀️ Heart, 😊 Smiley, 😒 Sad, πŸ˜‚ Laugh, 😠 Anger

Achievements

  • 25 predefined achievements across categories: distance milestones, update counts, trip duration, and social milestones (followers, friends)
  • Automatic unlock when thresholds are reached during trip updates
  • Per-user and per-trip achievement queries

Real-Time Updates

  • WebSocket endpoint at /ws with 24 event types
  • Live broadcasting to topic channels (/topic/trips/{tripId}, /topic/users/{userId})
  • Events for trip changes, comments, reactions, friend requests, follows, achievements, and polyline updates

Administration

  • Admin role promotion and demotion
  • Bootstrap admin mechanism for initial setup
  • Trip polyline and geocoding recomputation
  • Trip promotion with donation link support
  • Trip maintenance statistics dashboard
  • User and credential management

Weather

  • Google Weather API integration for live weather conditions at the current location

Email

  • Email verification on registration
  • Password reset with time-limited, one-time-use tokens
  • Configurable SMTP provider

πŸš€ Getting Started

Prerequisites

  • Java 21 β€” required
  • Maven 3.6+ β€” required
  • Docker β€” optional, for running the full stack locally
  • PostgreSQL 16 β€” required if running without Docker

Build

# Build all modules
mvn clean install

# Build a single module
mvn clean install -pl wanderer-command

# Format code (run before committing)
mvn spotless:apply

# Run tests with coverage
mvn clean verify

Run Locally

Start each service individually:

# Auth (Port 8083)
mvn spring-boot:run -pl wanderer-auth

# Command (Port 8081)
mvn spring-boot:run -pl wanderer-command

# Query (Port 8082)
mvn spring-boot:run -pl wanderer-query

Or bring up the full stack with Docker Compose:

docker-compose up

This starts two PostgreSQL databases (one for auth, one for command/query), and all three services with sensible defaults.

Swagger UI

Once running, interactive API docs are available at:

🌐 API Overview

All endpoints are under /api/1. Below is a summary grouped by domain. For detailed request/response examples, see the Wiki.

Authentication β€” wanderer-auth Β· Port 8083

Method Endpoint Description
POST /api/1/auth/register Register a new user (triggers email verification)
POST /api/1/auth/verify-email Verify email with token
POST /api/1/auth/login Login, returns access and refresh tokens
POST /api/1/auth/logout Logout, blacklists tokens πŸ”’
POST /api/1/auth/refresh Exchange refresh token for new token pair
POST /api/1/auth/password/reset Request a password reset email
POST /api/1/auth/password/reset-form Complete password reset with token
PUT /api/1/auth/password/change Change password (authenticated) πŸ”’

Users β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/users Create user
PATCH /api/1/users/me Update own profile πŸ”’
DELETE /api/1/users/me Delete own account πŸ”’

Users β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/users/{id} Get user by ID
GET /api/1/users/username/{username} Get user by username
GET /api/1/users/me Get current user profile πŸ”’
GET /api/1/users List all users with stats (admin) πŸ”’

Friends & Follows β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/users/friends/requests Send friend request πŸ”’
POST /api/1/users/friends/requests/{id}/accept Accept friend request πŸ”’
DELETE /api/1/users/friends/requests/{id} Decline/cancel friend request πŸ”’
DELETE /api/1/users/friends/{friendId} Remove friendship πŸ”’
POST /api/1/users/follows Follow a user πŸ”’
DELETE /api/1/users/follows/{followedId} Unfollow a user πŸ”’

Friends & Follows β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/users/me/friends Get my friends πŸ”’
GET /api/1/users/{userId}/friends Get user's friends
GET /api/1/users/friends/requests/received Received friend requests πŸ”’
GET /api/1/users/friends/requests/sent Sent friend requests πŸ”’
GET /api/1/users/me/following Users I follow πŸ”’
GET /api/1/users/me/followers My followers πŸ”’
GET /api/1/users/{userId}/following Users a given user follows
GET /api/1/users/{userId}/followers Followers of a given user

Trips β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/trips Create trip πŸ”’
POST /api/1/trips/from-plan/{tripPlanId} Create trip from plan πŸ”’
PUT /api/1/trips/{id} Update trip πŸ”’
PATCH /api/1/trips/{id}/visibility Change visibility πŸ”’
PATCH /api/1/trips/{id}/status Change status πŸ”’
PATCH /api/1/trips/{id}/settings Update settings πŸ”’
PATCH /api/1/trips/{id}/toggle-day Toggle day (multi-day trips) πŸ”’
DELETE /api/1/trips/{id} Delete trip πŸ”’

Trips β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/trips/{id} Get trip by ID
GET /api/1/trips/me Get my trips πŸ”’
GET /api/1/trips/me/available Get trips available to me πŸ”’
GET /api/1/trips/users/{userId} Get trips by user ID
GET /api/1/trips/public Get public trips
GET /api/1/trips List all trips (admin) πŸ”’

Trip Plans β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/trips/plans Create trip plan πŸ”’
PUT /api/1/trips/plans/{planId} Update trip plan πŸ”’
DELETE /api/1/trips/plans/{planId} Delete trip plan πŸ”’

Trip Plans β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/trips/plans/{planId} Get trip plan
GET /api/1/trips/plans/me Get my trip plans πŸ”’

Trip Updates β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/trips/{tripId}/updates Post a location update πŸ”’

Trip Updates β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/trips/{tripId}/updates Get updates for a trip
GET /api/1/trips/updates/{id} Get a single update by ID

Comments & Reactions β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/trips/{tripId}/comments Create comment or reply πŸ”’
POST /api/1/comments/{commentId}/reactions Add reaction to comment πŸ”’
DELETE /api/1/comments/{commentId}/reactions Remove reaction πŸ”’

Comments β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/trips/{tripId}/comments Get comments for a trip
GET /api/1/comments/{id} Get comment by ID

Achievements β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/achievements List all achievements
GET /api/1/users/me/achievements Get my achievements πŸ”’
GET /api/1/users/{userId}/achievements Get user's achievements
GET /api/1/trips/{tripId}/achievements Get trip's achievements

Promoted Trips β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/promoted-trips Get featured/promoted trips

Admin β€” wanderer-command Β· Port 8081

Method Endpoint Description
POST /api/1/admin/users/{userId}/promote Promote user to admin πŸ”’
DELETE /api/1/admin/users/{userId}/promote Demote admin πŸ”’
DELETE /api/1/admin/users/{userId} Delete user πŸ”’
POST /api/1/admin/trips/{tripId}/recompute-polyline Recompute polyline πŸ”’
POST /api/1/admin/trips/{tripId}/recompute-geocoding Recompute geocoding πŸ”’
POST /api/1/admin/trips/{tripId}/promote Promote trip πŸ”’
PUT /api/1/admin/trips/{tripId}/promote Update promotion / donation link πŸ”’
DELETE /api/1/admin/trips/{tripId}/promote Unpromote trip πŸ”’
POST /api/1/admin/trip-plans/{tripPlanId}/recompute-polyline Recompute plan polyline πŸ”’

Admin β€” wanderer-query Β· Port 8082

Method Endpoint Description
GET /api/1/admin/trips/stats Trip maintenance statistics πŸ”’

πŸ”’ = Requires authentication (JWT Bearer token). Admin endpoints also require the ADMIN role.

πŸ“‘ Real-Time Events

The command service exposes a WebSocket endpoint at /ws. Clients subscribe to topic channels and receive JSON messages as events occur.

Channel Events
/topic/trips/{tripId} TRIP_UPDATED, TRIP_STATUS_CHANGED, TRIP_VISIBILITY_CHANGED, TRIP_METADATA_UPDATED, TRIP_SETTINGS_UPDATED, TRIP_DELETED, COMMENT_ADDED, COMMENT_REACTION_ADDED, COMMENT_REACTION_REMOVED, COMMENT_REACTION_REPLACED, ACHIEVEMENT_UNLOCKED, POLYLINE_UPDATED
/topic/users/{userId} TRIP_CREATED, TRIP_PLAN_CREATED, TRIP_PLAN_UPDATED, TRIP_PLAN_DELETED, FRIEND_REQUEST_SENT, FRIEND_REQUEST_RECEIVED, FRIEND_REQUEST_ACCEPTED, FRIEND_REQUEST_DECLINED, FRIEND_REQUEST_CANCELLED, USER_FOLLOWED, USER_UNFOLLOWED

πŸ—„οΈ Data Model

Core Entities

Entity Key Fields Description
User id (UUID), username, userDetails (display name, bio, avatar) An authenticated user
Trip id, name, userId, tripSettings, tripDetails, encodedPolyline A walking journey with settings, details, and location history
TripDay id, tripId, dayNumber, startTimestamp, endTimestamp A single day within a multi-day trip
TripUpdate id, tripId, location (JSONB), battery, message, city, country, updateType A location/status post during a trip
TripPlan id, name, planType, startLocation, endLocation, metadata (JSONB) A route plan that can be turned into a trip
Comment id, tripId, userId, content, parentCommentId, replies A comment on a trip, with optional nested replies
CommentReaction id, commentId, userId, reactionType A user's reaction on a comment
Achievement id, type, name, description, thresholdValue A system-defined achievement template
UserAchievement id, userId, achievementId, tripId, unlockedAt A user's earned achievement
Friendship id, userId, friendId A confirmed bidirectional friend relationship
FriendRequest id, senderId, receiverId, status A pending, accepted, or declined friend request
UserFollow id, followerId, followedId A one-directional follow relationship
PromotedTrip id, tripId, donationLink, isPreAnnounced, countdownStartDate A featured trip shown in discovery

Enums

Enum Values
TripVisibility PUBLIC, PRIVATE, PROTECTED
TripStatus CREATED, IN_PROGRESS, PAUSED, RESTING, FINISHED
TripModality SIMPLE, MULTI_DAY
TripPlanType SIMPLE, MULTI_DAY
UpdateType REGULAR, DAY_START, DAY_END, TRIP_STARTED, TRIP_ENDED
ReactionType HEART, SMILEY, SAD, LAUGH, ANGER
FriendRequestStatus PENDING, ACCEPTED, DECLINED

πŸ”’ Security

Authentication Flow

  1. Register β†’ email verification link sent β†’ verify email β†’ account activated
  2. Login β†’ returns a short-lived access token (15 min default) and a long-lived refresh token (7 days)
  3. Refresh β†’ exchange a valid refresh token for a new token pair (rotation policy β€” old refresh token is revoked)
  4. Logout β†’ access token blacklisted by JTI, all refresh tokens revoked

Authorization

  • Role-based access: USER and ADMIN roles enforced with Spring Security @PreAuthorize
  • Protected trips visible only to friends of the owner
  • Public endpoints: registration, login, token refresh, password reset, public trips, user profiles
  • Admin endpoints: user promotion, trip maintenance, statistics

Token Storage

All tokens (refresh, password reset, blacklist) are hashed with SHA-256 before being stored in the database. Expired tokens are cleaned up automatically.

🐳 Deployment

Docker Compose

The included docker-compose.yml runs the full stack:

  • postgres-cqrs β€” PostgreSQL for command and query services (port 5432)
  • postgres-auth β€” PostgreSQL for the auth service (port 5433)
  • wanderer-command β€” Command service (port 8081)
  • wanderer-query β€” Query service (port 8082)
  • wanderer-auth β€” Auth service (port 8083)
docker-compose up

Building Docker Images

mvn clean compile jib:dockerBuild -pl wanderer-command
mvn clean compile jib:dockerBuild -pl wanderer-query
mvn clean compile jib:dockerBuild -pl wanderer-auth

Images are also published to ghcr.io/tomassirio/ via CI.

Kubernetes

The project is designed for Kubernetes deployment with Helm charts covering services, ConfigMaps, Secrets, Ingress, and health probes.

πŸ“š Documentation

Resource Description
API Wiki Full API reference with request/response examples
Docker Guide Building images and running with Docker Compose
CI/CD Workflows GitHub Actions pipelines for builds, releases, and publishing
Admin Roles Promoting users to admin, bootstrap configuration
Email Configuration SMTP setup for verification and password reset emails
Weather Integration Google Weather API setup
Comment Reactions Frontend integration guide for comment reactions
Release Notes Version history and changelog

Swagger UI is available on each running service at /swagger-ui.html.

🀝 Contributing

This is a personal project for my pilgrimage, but suggestions and improvements are welcome. Open an issue or submit a pull request.

Before submitting code, please run:

mvn spotless:apply   # Format code
mvn clean verify     # Run tests and check coverage (target: 80%+)

πŸ“ License

This project is licensed under the MIT License. You are free to use, modify, and distribute this software.


Β‘Buen Camino! πŸ₯Ύβ›ͺ

Packages

 
 
 

Contributors