Spring multi-module platform for creating, voting, and managing polls with scheduled winner notification workflows.
The platform allows users to:
- register and login with JWT
- create polls with expiration date (
expiresAt) - manage poll options
- vote one option per poll (vote replacement supported)
- receive winner notification at poll expiration through RabbitMQ message
Batch behavior:
- expire due polls
- compute winner and winner percentage
- publish winner payload to queue
poll.winner.mail - mark poll as notified (idempotent)
poll-domain: shared domain model, repositoriespoll-rest-service: REST APIs + JWT auth + Flyway migrationspoll-batch-service: scheduler + winner computation + Rabbit producerpostman/pollsystem_full_tests.postman_collection.json: API test collection
- Java 21
- Spring Boot 4.0.2
- Spring Web / Security / Data JPA
- PostgreSQL
- Flyway
- RabbitMQ
- Maven wrapper (
./mvnw)
- JDK 21
- Docker + Docker Compose
- Maven wrapper executable (
./mvnw)
From project root:
docker compose up -dServices:
- PostgreSQL:
localhost:5434(poll_system/poll_user/poll_pass) - RabbitMQ broker:
localhost:5672 - RabbitMQ UI:
http://localhost:15672(guest/guest)
./mvnw clean install -DskipTests./mvnw -f poll-rest-service/pom.xml spring-boot:run- Base URL:
http://localhost:8080/rest/api/v0 - Flyway is enabled here and applies migrations at startup.
./mvnw -f poll-batch-service/pom.xml spring-boot:run- Port:
8081 - Nightly schedule:
@Scheduled(cron = "0 0 0 * * *", zone = "Europe/Rome") - Batch logs include expired/notified counters and elapsed time.
The debug endpoint is available only with local profile.
Run batch with local profile:
./mvnw -f poll-batch-service/pom.xml spring-boot:run -Dspring-boot.run.profiles=localTrigger immediately:
curl -X POST http://localhost:8081/internal/batch/run-nowPowerShell alternative:
Invoke-RestMethod -Method POST -Uri "http://localhost:8081/internal/batch/run-now"POST /registrationPOST /loginPOST /pollsPOST /polls/{id}/optionsPUT /polls/{id}/options/{optionId}/vote
Auth header:
Authorization: Bearer <jwt>
Import:
postman/pollsystem_full_tests.postman_collection.json
Collection includes:
- auth tests
- poll CRUD tests
- options and vote tests
- post-vote negative tests (update/delete voted option must fail)
expiresAt is normalized in business timezone (Europe/Rome) to next midnight boundary.
Practical effect:
- creating a poll with near-future instant still expires at midnight logic
- for fast local batch testing, force DB expiration manually
Example:
UPDATE polls
SET expires_at = now() - interval '1 minute'
WHERE id = <poll_id>;After batch run, verify in DB:
SELECT id, status, winner_option_id, winner_percent, winner_notified_at, expires_at
FROM polls
ORDER BY id DESC
LIMIT 20;Expected:
status = EXPIRED- winner fields filled (or null when no votes)
winner_notified_atset after successful publish
RabbitMQ verification:
- queue:
poll.winner.mail - message payload fields:
pollQuestionwinnerOptionwinnerPercentexpiredAtownerEmail
Migrations live in:
poll-rest-service/src/main/resources/db/migration
Current scripts:
V1__init.sqlV2__add_winner_notified_at.sql
If schema is out-of-sync, start REST service and check Flyway logs.
Cause: running plugin on aggregator POM.
Use module command instead:
./mvnw -f poll-rest-service/pom.xml spring-boot:run
./mvnw -f poll-batch-service/pom.xml spring-boot:runInstall modules first:
./mvnw clean install -DskipTests- ensure batch runs on
8081 - ensure profile is
local(endpoint is profile-scoped)
Rebuild all modules and restart app:
./mvnw clean install -DskipTests- DB timestamps are stored in UTC (
hibernate.jdbc.time_zone=UTC). - Batch and business date conversion use
Europe/Romewhere required. - Queue publishing is decoupled from email sending (consumer is external).