{"id":46597801,"url":"https://github.com/tomassirio/wanderer-backend","last_synced_at":"2026-04-01T17:24:37.655Z","repository":{"id":317745918,"uuid":"1068633337","full_name":"tomassirio/wanderer-backend","owner":"tomassirio","description":"Real-time pilgrimage tracking App - Backend Repository","archived":false,"fork":false,"pushed_at":"2026-03-30T12:11:54.000Z","size":2212,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-30T12:17:16.988Z","etag":null,"topics":["cqrs","docker","docker-compose","helm","java","kubernetes","pilgrim","pilgrimage","postgresql","springboot","walking"],"latest_commit_sha":null,"homepage":"https://wanderer.tomassir.io/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomassirio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-10-02T17:14:38.000Z","updated_at":"2026-03-30T11:28:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"34dd3376-65b6-4363-b4ed-39ffda262839","html_url":"https://github.com/tomassirio/wanderer-backend","commit_stats":null,"previous_names":["tomassirio/tracker-backend","tomassirio/wanderer-backend"],"tags_count":79,"template":false,"template_full_name":null,"purl":"pkg:github/tomassirio/wanderer-backend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomassirio%2Fwanderer-backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomassirio%2Fwanderer-backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomassirio%2Fwanderer-backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomassirio%2Fwanderer-backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomassirio","download_url":"https://codeload.github.com/tomassirio/wanderer-backend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomassirio%2Fwanderer-backend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290537,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cqrs","docker","docker-compose","helm","java","kubernetes","pilgrim","pilgrimage","postgresql","springboot","walking"],"created_at":"2026-03-07T15:04:37.937Z","updated_at":"2026-04-01T17:24:37.647Z","avatar_url":"https://github.com/tomassirio.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/tomassirio/wanderer-backend/main/wanderer-auth/src/main/resources/assets/wanderer-logo.png\" alt=\"Wanderer Logo\" width=\"400\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eReal-time pilgrimage tracking — Utrecht to Santiago de Compostela\u003c/strong\u003e\n\u003c/p\u003e\n\n![Java](https://img.shields.io/badge/Java-21-orange?logo=openjdk\u0026logoColor=white)\n![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.6-brightgreen?logo=spring\u0026logoColor=white)\n![Version](https://img.shields.io/badge/version-1.2.2-blue)\n![Build Status](https://img.shields.io/github/actions/workflow/status/tomassirio/wanderer-backend/merge.yml?branch=main\u0026label=build)\n![License](https://img.shields.io/badge/license-MIT-green)\n![Coverage](https://img.shields.io/badge/coverage-50%25-orange)\n\n![PostgresSQL](https://img.shields.io/badge/PostgreSQL-16-336791?logo=postgresql\u0026logoColor=white)\n![Docker](https://img.shields.io/badge/docker-enabled-2496ED?logo=docker\u0026logoColor=white)\n![Kubernetes](https://img.shields.io/badge/kubernetes-ready-326CE5?logo=kubernetes\u0026logoColor=white)\n![Architecture](https://img.shields.io/badge/architecture-CQRS-blueviolet)\n\nWanderer 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.\n\nThe 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.\n\n## Table of Contents\n\n- [Architecture](#-architecture)\n- [Features](#-features)\n- [Getting Started](#-getting-started)\n- [API Overview](#-api-overview)\n- [Real-Time Events](#-real-time-events)\n- [Data Model](#-data-model)\n- [Security](#-security)\n- [Deployment](#-deployment)\n- [Documentation](#-documentation)\n- [Contributing](#-contributing)\n- [License](#-license)\n\n## 🏗️ Architecture\n\nThe backend is a multi-module Maven project split by responsibility:\n\n```\nwanderer-backend/\n├── commons/            Shared domain entities, DTOs, mappers, and constants\n├── wanderer-auth/      Authentication \u0026 authorization          → Port 8083\n├── wanderer-command/   Write operations (create, update, delete) → Port 8081\n├── wanderer-query/     Read operations (queries \u0026 lists)        → Port 8082\n├── docs/               Additional guides and documentation\n└── docker-compose.yml  Full local stack with two Postgres instances\n```\n\n| Layer | Responsibility |\n|-------|---------------|\n| **commons** | Domain entities, DTOs, MapStruct mappers, ApiConstants, enums, exceptions |\n| **wanderer-auth** | Registration, login, JWT issuance, refresh tokens, password reset, email verification, admin role management |\n| **wanderer-command** | Trip CRUD, trip updates (location posts), comments, reactions, friend requests, follows, admin maintenance, WebSocket event broadcasting |\n| **wanderer-query** | Trip queries, user lookups, comment retrieval, achievement listings, promoted trips, friendship/follow queries, admin statistics |\n\n### Technology Stack\n\n| Category | Technology |\n|----------|-----------|\n| Language | Java 21 |\n| Framework | Spring Boot 3.5.6 |\n| Database | PostgreSQL 16 with Liquibase migrations |\n| Security | JWT (access + refresh tokens), bcrypt, role-based authorization |\n| Real-Time | WebSocket (native Spring WebSocket) |\n| DTO Mapping | MapStruct |\n| External APIs | Google Maps (polylines, geocoding, weather) |\n| Email | SMTP (configurable, Brevo/Sendinblue by default) |\n| Code Style | Spotless with Google Java Format (AOSP) |\n| Testing | JUnit 5, Cucumber (BDD), JaCoCo (coverage) |\n| Containers | Docker via Jib Maven Plugin |\n| Orchestration | Kubernetes + Helm |\n| API Docs | SpringDoc OpenAPI (Swagger UI) |\n\n## ✨ Features\n\n### Trip Tracking\n- Create trips with configurable visibility (Public, Private, Protected) and modality (Simple or Multi-Day)\n- Post location updates with GPS coordinates, altitude, battery level, and messages\n- Automatic polyline generation from location history\n- Reverse geocoding of coordinates into city and country\n- Day-by-day tracking for multi-day trips with start/end day toggling\n- Trip status lifecycle: Created → In Progress → Paused / Resting → Finished\n\n### Social\n- Friend requests with accept/decline workflow\n- Bidirectional friendships that unlock access to Protected trips\n- User follows for one-directional social connections\n- Public trip discovery and promoted/featured trips\n\n### Comments \u0026 Reactions\n- Threaded comments on trips with one level of nesting (replies)\n- Five reaction types on comments: ❤️ Heart, 😊 Smiley, 😢 Sad, 😂 Laugh, 😠 Anger\n\n### Achievements\n- 25 predefined achievements across categories: distance milestones, update counts, trip duration, and social milestones (followers, friends)\n- Automatic unlock when thresholds are reached during trip updates\n- Per-user and per-trip achievement queries\n\n### Real-Time Updates\n- WebSocket endpoint at `/ws` with 24 event types\n- Live broadcasting to topic channels (`/topic/trips/{tripId}`, `/topic/users/{userId}`)\n- Events for trip changes, comments, reactions, friend requests, follows, achievements, and polyline updates\n\n### Administration\n- Admin role promotion and demotion\n- Bootstrap admin mechanism for initial setup\n- Trip polyline and geocoding recomputation\n- Trip promotion with donation link support\n- Trip maintenance statistics dashboard\n- User and credential management\n\n### Weather\n- Google Weather API integration for live weather conditions at the current location\n\n### Email\n- Email verification on registration\n- Password reset with time-limited, one-time-use tokens\n- Configurable SMTP provider\n\n## 🚀 Getting Started\n\n### Prerequisites\n\n- **Java 21** — required\n- **Maven 3.6+** — required\n- **Docker** — optional, for running the full stack locally\n- **PostgreSQL 16** — required if running without Docker\n\n### Build\n\n```bash\n# Build all modules\nmvn clean install\n\n# Build a single module\nmvn clean install -pl wanderer-command\n\n# Format code (run before committing)\nmvn spotless:apply\n\n# Run tests with coverage\nmvn clean verify\n```\n\n### Run Locally\n\nStart each service individually:\n\n```bash\n# Auth (Port 8083)\nmvn spring-boot:run -pl wanderer-auth\n\n# Command (Port 8081)\nmvn spring-boot:run -pl wanderer-command\n\n# Query (Port 8082)\nmvn spring-boot:run -pl wanderer-query\n```\n\nOr bring up the full stack with Docker Compose:\n\n```bash\ndocker-compose up\n```\n\nThis starts two PostgreSQL databases (one for auth, one for command/query), and all three services with sensible defaults.\n\n### Swagger UI\n\nOnce running, interactive API docs are available at:\n\n- Auth: http://localhost:8083/swagger-ui.html\n- Command: http://localhost:8081/swagger-ui.html\n- Query: http://localhost:8082/swagger-ui.html\n\n## 🌐 API Overview\n\nAll endpoints are under `/api/1`. Below is a summary grouped by domain. For detailed request/response examples, see the [Wiki](https://github.com/tomassirio/wanderer-backend/wiki).\n\n### Authentication — `wanderer-auth` · Port 8083\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/auth/register` | Register a new user (triggers email verification) |\n| POST | `/api/1/auth/verify-email` | Verify email with token |\n| POST | `/api/1/auth/login` | Login, returns access and refresh tokens |\n| POST | `/api/1/auth/logout` | Logout, blacklists tokens 🔒 |\n| POST | `/api/1/auth/refresh` | Exchange refresh token for new token pair |\n| POST | `/api/1/auth/password/reset` | Request a password reset email |\n| POST | `/api/1/auth/password/reset-form` | Complete password reset with token |\n| PUT | `/api/1/auth/password/change` | Change password (authenticated) 🔒 |\n\n### Users — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/users` | Create user |\n| PATCH | `/api/1/users/me` | Update own profile 🔒 |\n| DELETE | `/api/1/users/me` | Delete own account 🔒 |\n\n### Users — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/users/{id}` | Get user by ID |\n| GET | `/api/1/users/username/{username}` | Get user by username |\n| GET | `/api/1/users/me` | Get current user profile 🔒 |\n| GET | `/api/1/users` | List all users with stats (admin) 🔒 |\n\n### Friends \u0026 Follows — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/users/friends/requests` | Send friend request 🔒 |\n| POST | `/api/1/users/friends/requests/{id}/accept` | Accept friend request 🔒 |\n| DELETE | `/api/1/users/friends/requests/{id}` | Decline/cancel friend request 🔒 |\n| DELETE | `/api/1/users/friends/{friendId}` | Remove friendship 🔒 |\n| POST | `/api/1/users/follows` | Follow a user 🔒 |\n| DELETE | `/api/1/users/follows/{followedId}` | Unfollow a user 🔒 |\n\n### Friends \u0026 Follows — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/users/me/friends` | Get my friends 🔒 |\n| GET | `/api/1/users/{userId}/friends` | Get user's friends |\n| GET | `/api/1/users/friends/requests/received` | Received friend requests 🔒 |\n| GET | `/api/1/users/friends/requests/sent` | Sent friend requests 🔒 |\n| GET | `/api/1/users/me/following` | Users I follow 🔒 |\n| GET | `/api/1/users/me/followers` | My followers 🔒 |\n| GET | `/api/1/users/{userId}/following` | Users a given user follows |\n| GET | `/api/1/users/{userId}/followers` | Followers of a given user |\n\n### Trips — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/trips` | Create trip 🔒 |\n| POST | `/api/1/trips/from-plan/{tripPlanId}` | Create trip from plan 🔒 |\n| PUT | `/api/1/trips/{id}` | Update trip 🔒 |\n| PATCH | `/api/1/trips/{id}/visibility` | Change visibility 🔒 |\n| PATCH | `/api/1/trips/{id}/status` | Change status 🔒 |\n| PATCH | `/api/1/trips/{id}/settings` | Update settings 🔒 |\n| PATCH | `/api/1/trips/{id}/toggle-day` | Toggle day (multi-day trips) 🔒 |\n| DELETE | `/api/1/trips/{id}` | Delete trip 🔒 |\n\n### Trips — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/trips/{id}` | Get trip by ID |\n| GET | `/api/1/trips/me` | Get my trips 🔒 |\n| GET | `/api/1/trips/me/available` | Get trips available to me 🔒 |\n| GET | `/api/1/trips/users/{userId}` | Get trips by user ID |\n| GET | `/api/1/trips/public` | Get public trips |\n| GET | `/api/1/trips` | List all trips (admin) 🔒 |\n\n### Trip Plans — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/trips/plans` | Create trip plan 🔒 |\n| PUT | `/api/1/trips/plans/{planId}` | Update trip plan 🔒 |\n| DELETE | `/api/1/trips/plans/{planId}` | Delete trip plan 🔒 |\n\n### Trip Plans — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/trips/plans/{planId}` | Get trip plan |\n| GET | `/api/1/trips/plans/me` | Get my trip plans 🔒 |\n\n### Trip Updates — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/trips/{tripId}/updates` | Post a location update 🔒 |\n\n### Trip Updates — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/trips/{tripId}/updates` | Get updates for a trip |\n| GET | `/api/1/trips/updates/{id}` | Get a single update by ID |\n\n### Comments \u0026 Reactions — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/trips/{tripId}/comments` | Create comment or reply 🔒 |\n| POST | `/api/1/comments/{commentId}/reactions` | Add reaction to comment 🔒 |\n| DELETE | `/api/1/comments/{commentId}/reactions` | Remove reaction 🔒 |\n\n### Comments — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/trips/{tripId}/comments` | Get comments for a trip |\n| GET | `/api/1/comments/{id}` | Get comment by ID |\n\n### Achievements — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/achievements` | List all achievements |\n| GET | `/api/1/users/me/achievements` | Get my achievements 🔒 |\n| GET | `/api/1/users/{userId}/achievements` | Get user's achievements |\n| GET | `/api/1/trips/{tripId}/achievements` | Get trip's achievements |\n\n### Promoted Trips — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/promoted-trips` | Get featured/promoted trips |\n\n### Admin — `wanderer-command` · Port 8081\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| POST | `/api/1/admin/users/{userId}/promote` | Promote user to admin 🔒 |\n| DELETE | `/api/1/admin/users/{userId}/promote` | Demote admin 🔒 |\n| DELETE | `/api/1/admin/users/{userId}` | Delete user 🔒 |\n| POST | `/api/1/admin/trips/{tripId}/recompute-polyline` | Recompute polyline 🔒 |\n| POST | `/api/1/admin/trips/{tripId}/recompute-geocoding` | Recompute geocoding 🔒 |\n| POST | `/api/1/admin/trips/{tripId}/promote` | Promote trip 🔒 |\n| PUT | `/api/1/admin/trips/{tripId}/promote` | Update promotion / donation link 🔒 |\n| DELETE | `/api/1/admin/trips/{tripId}/promote` | Unpromote trip 🔒 |\n| POST | `/api/1/admin/trip-plans/{tripPlanId}/recompute-polyline` | Recompute plan polyline 🔒 |\n\n### Admin — `wanderer-query` · Port 8082\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/1/admin/trips/stats` | Trip maintenance statistics 🔒 |\n\n\u003e 🔒 = Requires authentication (JWT Bearer token). Admin endpoints also require the `ADMIN` role.\n\n## 📡 Real-Time Events\n\nThe command service exposes a WebSocket endpoint at `/ws`. Clients subscribe to topic channels and receive JSON messages as events occur.\n\n| Channel | Events |\n|---------|--------|\n| `/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` |\n| `/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` |\n\n## 🗄️ Data Model\n\n### Core Entities\n\n| Entity | Key Fields | Description |\n|--------|-----------|-------------|\n| **User** | `id` (UUID), `username`, `userDetails` (display name, bio, avatar) | An authenticated user |\n| **Trip** | `id`, `name`, `userId`, `tripSettings`, `tripDetails`, `encodedPolyline` | A walking journey with settings, details, and location history |\n| **TripDay** | `id`, `tripId`, `dayNumber`, `startTimestamp`, `endTimestamp` | A single day within a multi-day trip |\n| **TripUpdate** | `id`, `tripId`, `location` (JSONB), `battery`, `message`, `city`, `country`, `updateType` | A location/status post during a trip |\n| **TripPlan** | `id`, `name`, `planType`, `startLocation`, `endLocation`, `metadata` (JSONB) | A route plan that can be turned into a trip |\n| **Comment** | `id`, `tripId`, `userId`, `content`, `parentCommentId`, `replies` | A comment on a trip, with optional nested replies |\n| **CommentReaction** | `id`, `commentId`, `userId`, `reactionType` | A user's reaction on a comment |\n| **Achievement** | `id`, `type`, `name`, `description`, `thresholdValue` | A system-defined achievement template |\n| **UserAchievement** | `id`, `userId`, `achievementId`, `tripId`, `unlockedAt` | A user's earned achievement |\n| **Friendship** | `id`, `userId`, `friendId` | A confirmed bidirectional friend relationship |\n| **FriendRequest** | `id`, `senderId`, `receiverId`, `status` | A pending, accepted, or declined friend request |\n| **UserFollow** | `id`, `followerId`, `followedId` | A one-directional follow relationship |\n| **PromotedTrip** | `id`, `tripId`, `donationLink`, `isPreAnnounced`, `countdownStartDate` | A featured trip shown in discovery |\n\n### Enums\n\n| Enum | Values |\n|------|--------|\n| TripVisibility | `PUBLIC`, `PRIVATE`, `PROTECTED` |\n| TripStatus | `CREATED`, `IN_PROGRESS`, `PAUSED`, `RESTING`, `FINISHED` |\n| TripModality | `SIMPLE`, `MULTI_DAY` |\n| TripPlanType | `SIMPLE`, `MULTI_DAY` |\n| UpdateType | `REGULAR`, `DAY_START`, `DAY_END`, `TRIP_STARTED`, `TRIP_ENDED` |\n| ReactionType | `HEART`, `SMILEY`, `SAD`, `LAUGH`, `ANGER` |\n| FriendRequestStatus | `PENDING`, `ACCEPTED`, `DECLINED` |\n\n## 🔒 Security\n\n### Authentication Flow\n\n1. **Register** → email verification link sent → **verify email** → account activated\n2. **Login** → returns a short-lived access token (15 min default) and a long-lived refresh token (7 days)\n3. **Refresh** → exchange a valid refresh token for a new token pair (rotation policy — old refresh token is revoked)\n4. **Logout** → access token blacklisted by JTI, all refresh tokens revoked\n\n### Authorization\n\n- Role-based access: `USER` and `ADMIN` roles enforced with Spring Security `@PreAuthorize`\n- Protected trips visible only to friends of the owner\n- Public endpoints: registration, login, token refresh, password reset, public trips, user profiles\n- Admin endpoints: user promotion, trip maintenance, statistics\n\n### Token Storage\n\nAll tokens (refresh, password reset, blacklist) are hashed with SHA-256 before being stored in the database. Expired tokens are cleaned up automatically.\n\n## 🐳 Deployment\n\n### Docker Compose\n\nThe included `docker-compose.yml` runs the full stack:\n\n- **postgres-cqrs** — PostgreSQL for command and query services (port 5432)\n- **postgres-auth** — PostgreSQL for the auth service (port 5433)\n- **wanderer-command** — Command service (port 8081)\n- **wanderer-query** — Query service (port 8082)\n- **wanderer-auth** — Auth service (port 8083)\n\n```bash\ndocker-compose up\n```\n\n### Building Docker Images\n\n```bash\nmvn clean compile jib:dockerBuild -pl wanderer-command\nmvn clean compile jib:dockerBuild -pl wanderer-query\nmvn clean compile jib:dockerBuild -pl wanderer-auth\n```\n\nImages are also published to `ghcr.io/tomassirio/` via CI.\n\n### Kubernetes\n\nThe project is designed for Kubernetes deployment with Helm charts covering services, ConfigMaps, Secrets, Ingress, and health probes.\n\n## 📚 Documentation\n\n| Resource | Description |\n|----------|-------------|\n| [API Wiki](https://github.com/tomassirio/wanderer-backend/wiki) | Full API reference with request/response examples |\n| [Docker Guide](docs/DOCKER.md) | Building images and running with Docker Compose |\n| [CI/CD Workflows](docs/CI-CD.md) | GitHub Actions pipelines for builds, releases, and publishing |\n| [Admin Roles](docs/ADMIN_ROLES.md) | Promoting users to admin, bootstrap configuration |\n| [Email Configuration](docs/EMAIL_CONFIGURATION.md) | SMTP setup for verification and password reset emails |\n| [Weather Integration](docs/WEATHER_INTEGRATION.md) | Google Weather API setup |\n| [Comment Reactions](docs/FRONTEND_COMMENT_REACTIONS.md) | Frontend integration guide for comment reactions |\n| [Release Notes](https://github.com/tomassirio/wanderer-backend/releases) | Version history and changelog |\n\nSwagger UI is available on each running service at `/swagger-ui.html`.\n\n## 🤝 Contributing\n\nThis is a personal project for my pilgrimage, but suggestions and improvements are welcome. Open an issue or submit a pull request.\n\nBefore submitting code, please run:\n\n```bash\nmvn spotless:apply   # Format code\nmvn clean verify     # Run tests and check coverage (target: 80%+)\n```\n\n## 📝 License\n\nThis project is licensed under the [MIT License](LICENSE). You are free to use, modify, and distribute this software.\n\n---\n\n**¡Buen Camino!** 🥾⛪\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomassirio%2Fwanderer-backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomassirio%2Fwanderer-backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomassirio%2Fwanderer-backend/lists"}