Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
HELP.md
.claude/settings.local.json
target/
storage/*.db
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ Release names follow the **historic football clubs** naming convention (A–Z):

### Added

- Integrate Flyway for database schema versioning and automated migrations;
add `spring-boot-starter-flyway` (Spring Boot 4.0 requires this dedicated
starter for autoconfiguration — `flyway-core` alone is insufficient) and
`flyway-database-postgresql` to `pom.xml`; create
migration directory `src/main/resources/db/migration/` with three versioned
scripts: `V1__Create_players_table.sql` (schema), `V2__Seed_starting11.sql`
(11 Starting XI players), `V3__Seed_substitutes.sql` (15 substitute players);
configure `spring.flyway.enabled=true` and `spring.flyway.locations` only —
no baseline settings, Flyway runs V1→V2→V3 from scratch on every empty
database; disable Flyway in test environment which continues to use SQLite
in-memory with `ddl.sql`/`dml.sql`; switch `spring.jpa.hibernate.ddl-auto`
from `none` to `validate` so Hibernate verifies entity mappings against the
Flyway-managed schema (#130)

### Changed

- Switch runtime base image from `eclipse-temurin:25-jdk-alpine` to
Expand Down
5 changes: 0 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ COPY --chmod=444 README.md ./
COPY --chmod=555 scripts/entrypoint.sh ./entrypoint.sh
COPY --chmod=555 scripts/healthcheck.sh ./healthcheck.sh

# The 'hold' is our storage compartment within the image. Here, we copy a
# pre-seeded SQLite database file, which Compose will mount as a persistent
# 'storage' volume when the container starts up.
COPY --chmod=555 storage/ ./hold/

# Install SQLite runtime libs, add non-root user and prepare volume mount point
RUN apk add --no-cache sqlite-libs && \
addgroup -S spring && \
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,53 @@ spring.datasource.url=jdbc:sqlite::memory:
spring.jpa.hibernate.ddl-auto=create-drop
```

## Database Migrations

Schema versioning is managed by [Flyway](https://documentation.red-gate.com/flyway), which runs automatically on application startup and applies any pending migrations in order.

### Migration files

Versioned SQL scripts live in `src/main/resources/db/migration/` and follow the Flyway naming convention:

```text
V{version}__{description}.sql
```

| File | Description |
| ---- | ----------- |
| `V1__Create_players_table.sql` | Creates the `players` table (schema) |
| `V2__Seed_starting11.sql` | Seeds 11 Starting XI players (`starting11 = 1`) |
| `V3__Seed_substitutes.sql` | Seeds 15 Substitute players (`starting11 = 0`) |

All migration SQL is written to be compatible with both **SQLite** (local dev) and **PostgreSQL** (see #286).

### First start

On first run, Flyway detects an empty database and applies V1 → V2 → V3 in sequence, creating the `players` table and seeding all 26 players. The database file (`storage/players-sqlite3.db`) is created automatically and is excluded from version control.

### Adding a new migration

Create a new file in `src/main/resources/db/migration/` with the next version number:

```bash
touch src/main/resources/db/migration/V4__Add_nationality_column.sql
```

Flyway applies it automatically on the next application startup. View the applied history by querying the `flyway_schema_history` table.

### Reset local database

Delete the SQLite file and restart — Flyway recreates the schema and seed data from scratch:

```bash
rm storage/players-sqlite3.db
./mvnw spring-boot:run
```

### Tests

The test environment keeps `spring.flyway.enabled=false` and uses SQLite in-memory with `ddl.sql`/`dml.sql` via Spring SQL init for fast, isolated test execution.

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
Expand Down
25 changes: 25 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,31 @@
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
</dependency>
<!-- Spring Boot Starter Flyway ==================================== -->
<!--
Spring Boot 4.0 moved Flyway auto-configuration out of
spring-boot-autoconfigure into this dedicated starter. Adding only
flyway-core is no longer sufficient — this starter is required to
enable FlywayAutoConfiguration and the spring.flyway.* properties.
Transitively brings in flyway-core (version managed by Spring BOM).
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-flyway
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<!-- Flyway Database PostgreSQL ==================================== -->
<!--
Flyway 10+ modular architecture requires explicit database support
modules for non-community databases. Required for PostgreSQL support
(see #286 — Add PostgreSQL support with unified migration-based
initialization).
https://mvnrepository.com/artifact/org.flywaydb/flyway-database-postgresql
-->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<!-- H2 Database Engine ============================================ -->
<!--
Provides a fast in-memory database for testing. Used only in test
Expand Down
59 changes: 30 additions & 29 deletions scripts/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
#!/bin/sh
set -e

echo "✔ Executing entrypoint script..."

IMAGE_STORAGE_PATH="/app/hold/players-sqlite3.db"
VOLUME_STORAGE_PATH="/storage/players-sqlite3.db"

echo "✔ Starting container..."

if [ ! -f "$VOLUME_STORAGE_PATH" ]; then
echo "⚠️ No existing database file found in volume."
if [ -f "$IMAGE_STORAGE_PATH" ]; then
echo "Copying database file to writable volume..."
if cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH"; then
echo "✔ Database initialized at $VOLUME_STORAGE_PATH"
else
echo "❌ Failed to copy database from $IMAGE_STORAGE_PATH to $VOLUME_STORAGE_PATH"
echo " Check file permissions and available disk space."
exit 1
fi
else
echo "⚠️ Database file missing at $IMAGE_STORAGE_PATH"
# Helper function for formatted logging
log() {
echo "[ENTRYPOINT] $(date '+%Y/%m/%d - %H:%M:%S') | $1"
return 0
}

log "✔ Starting container..."

STORAGE_PATH="${STORAGE_PATH:-storage/players-sqlite3.db}"

mkdir -p "$(dirname "$STORAGE_PATH")"

if [ ! -w "$(dirname "$STORAGE_PATH")" ]; then
log "❌ Storage directory is not writable: $(dirname "$STORAGE_PATH")"
exit 1
fi
fi

if [ -f "$STORAGE_PATH" ] && [ ! -w "$STORAGE_PATH" ]; then
log "❌ Database file is not writable: $STORAGE_PATH"
exit 1
fi

if [ ! -f "$STORAGE_PATH" ]; then
log "⚠️ No existing database file found at $STORAGE_PATH."
log "🗄️ Flyway migrations will initialize the database on first start."
else
echo "✔ Existing database file found. Skipping seed copy."
log "✔ Existing database file found at $STORAGE_PATH."
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

echo "✔ Ready!"
echo "🚀 Launching app..."
echo ""
echo "🔌 Endpoints:"
echo " Health: http://localhost:9001/actuator/health"
echo " Players: http://localhost:9000/players"
echo ""
log "✔ Ready!"
log "🚀 Launching app..."
log "🔌 API endpoints | http://localhost:9000"
log "📚 Swagger UI | http://localhost:9000/swagger/index.html"
log "❤️ Health check | http://localhost:9001/actuator/health"
exec "$@"
8 changes: 7 additions & 1 deletion src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ springdoc.swagger-ui.path=/swagger/index.html
spring.datasource.url=jdbc:sqlite:${STORAGE_PATH:storage/players-sqlite3.db}
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true

# Flyway Database Migration Configuration
# Flyway manages all schema creation and seed data via versioned SQL migrations.
# On first start, Flyway creates the database and runs V1 → V2 → V3 in order.
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
21 changes: 21 additions & 0 deletions src/main/resources/db/migration/V1__Create_players_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- V1: Create players table
-- Compatible with both SQLite (local dev) and PostgreSQL (see #286).
-- TEXT columns use TEXT affinity in SQLite and the unlimited TEXT type in PostgreSQL.
-- INTEGER is used for squadNumber (natural key) and starting11 (boolean flag: 1/0).
-- UUID primary key is stored as VARCHAR(36) and generated at the application level.

CREATE TABLE IF NOT EXISTS players (
id VARCHAR(36) NOT NULL,
squadNumber INTEGER NOT NULL,
firstName TEXT NOT NULL,
middleName TEXT,
lastName TEXT NOT NULL,
dateOfBirth TEXT NOT NULL,
position TEXT NOT NULL,
abbrPosition TEXT NOT NULL,
team TEXT NOT NULL,
league TEXT NOT NULL,
starting11 BOOLEAN NOT NULL,
PRIMARY KEY (id),
UNIQUE (squadNumber)
);
16 changes: 16 additions & 0 deletions src/main/resources/db/migration/V2__Seed_starting11.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- V2: Seed Starting XI players (starting11 = 1)
-- Argentina 2022 FIFA World Cup squad — 11 players who started the final.
-- Rolling back this migration removes only the Starting XI, leaving substitutes intact.

INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
('01772c59-43f0-5d85-b913-c78e4e281452', 23, 'Damián', 'Emiliano', 'Martínez', '1992-09-02T00:00:00.000Z', 'Goalkeeper', 'GK', 'Aston Villa FC', 'Premier League', 1),

Check failure on line 6 in src/main/resources/db/migration/V2__Seed_starting11.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zVofrTUmNjbgHJb&open=AZ131zVofrTUmNjbgHJb&pullRequest=310
('da31293b-4c7e-5e0f-a168-469ee29ecbc4', 26, 'Nahuel', NULL, 'Molina', '1998-04-06T00:00:00.000Z', 'Right-Back', 'RB', 'Atlético Madrid', 'La Liga', 1),
('c096c69e-762b-5281-9290-bb9c167a24a0', 13, 'Cristian','Gabriel', 'Romero', '1998-04-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Tottenham Hotspur', 'Premier League', 1),
('d5f7dd7a-1dcb-5960-ba27-e34865b63358', 19, 'Nicolás', 'Hernán Gonzalo', 'Otamendi', '1988-02-12T00:00:00.000Z', 'Centre-Back', 'CB', 'SL Benfica', 'Liga Portugal', 1),

Check failure on line 9 in src/main/resources/db/migration/V2__Seed_starting11.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 3 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zVofrTUmNjbgHJZ&open=AZ131zVofrTUmNjbgHJZ&pullRequest=310

Check failure on line 9 in src/main/resources/db/migration/V2__Seed_starting11.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 3 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zVofrTUmNjbgHJa&open=AZ131zVofrTUmNjbgHJa&pullRequest=310
('2f6f90a0-9b9d-5023-96d2-a2aaf03143a6', 3, 'Nicolás', 'Alejandro', 'Tagliafico','1992-08-31T00:00:00.000Z', 'Left-Back', 'LB', 'Olympique Lyon', 'Ligue 1', 1),
('b5b46e79-929e-5ed2-949d-0d167109c022', 11, 'Ángel', 'Fabián', 'Di María', '1988-02-14T00:00:00.000Z', 'Right Winger', 'RW', 'SL Benfica', 'Liga Portugal', 1),
('0293b282-1da8-562e-998e-83849b417a42', 7, 'Rodrigo', 'Javier', 'de Paul', '1994-05-24T00:00:00.000Z', 'Central Midfield','CM', 'Atlético Madrid', 'La Liga', 1),

Check failure on line 12 in src/main/resources/db/migration/V2__Seed_starting11.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 3 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zVofrTUmNjbgHJc&open=AZ131zVofrTUmNjbgHJc&pullRequest=310
('d3ba552a-dac3-588a-b961-1ea7224017fd', 24, 'Enzo', 'Jeremías', 'Fernández', '2001-01-17T00:00:00.000Z', 'Central Midfield','CM', 'SL Benfica', 'Liga Portugal', 1),
('9613cae9-16ab-5b54-937e-3135123b9e0d', 20, 'Alexis', NULL, 'Mac Allister','1998-12-24T00:00:00.000Z','Central Midfield','CM','Brighton & Hove Albion', 'Premier League', 1),
('acc433bf-d505-51fe-831e-45eb44c4d43c', 10, 'Lionel', 'Andrés', 'Messi', '1987-06-24T00:00:00.000Z', 'Right Winger', 'RW', 'Paris Saint-Germain', 'Ligue 1', 1),
('38bae91d-8519-55a2-b30a-b9fe38849bfb', 9, 'Julián', NULL, 'Álvarez', '2000-01-31T00:00:00.000Z', 'Centre-Forward', 'CF', 'Manchester City', 'Premier League', 1);
20 changes: 20 additions & 0 deletions src/main/resources/db/migration/V3__Seed_substitutes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- V3: Seed Substitute players (starting11 = 0)
-- Argentina 2022 FIFA World Cup squad — 15 players who did not start the final.
-- Rolling back this migration removes only the substitutes, leaving the Starting XI intact.

INSERT INTO players (id, squadNumber, firstName, middleName, lastName, dateOfBirth, position, abbrPosition, team, league, starting11) VALUES
('5a9cd988-95e6-54c1-bc34-9aa08acca8d0', 1, 'Franco', 'Daniel', 'Armani', '1986-10-16T00:00:00.000Z', 'Goalkeeper', 'GK', 'River Plate', 'Copa de la Liga', 0),
('5fdb10e8-38c0-5084-9a3f-b369a960b9c2', 2, 'Juan', 'Marcos', 'Foyth', '1998-01-12T00:00:00.000Z', 'Right-Back', 'RB', 'Villarreal', 'La Liga', 0),

Check failure on line 7 in src/main/resources/db/migration/V3__Seed_substitutes.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 5 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zSJfrTUmNjbgHJY&open=AZ131zSJfrTUmNjbgHJY&pullRequest=310
('bbd441f7-fcfb-5834-8468-2a9004b64c8c', 4, 'Gonzalo', 'Ariel', 'Montiel', '1997-01-01T00:00:00.000Z', 'Right-Back', 'RB', 'Nottingham Forest', 'Premier League', 0),
('9d140400-196f-55d8-86e1-e0b96a375c83', 5, 'Leandro', 'Daniel', 'Paredes', '1994-06-29T00:00:00.000Z', 'Defensive Midfield','DM', 'AS Roma', 'Serie A', 0),

Check failure on line 9 in src/main/resources/db/migration/V3__Seed_substitutes.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal 4 times.

See more on https://sonarcloud.io/project/issues?id=nanotaboada_java.samples.spring.boot&issues=AZ131zSJfrTUmNjbgHJX&open=AZ131zSJfrTUmNjbgHJX&pullRequest=310
('d8bfea25-f189-5d5e-b3a5-ed89329b9f7c', 6, 'Germán', 'Alejo', 'Pezzella', '1991-06-27T00:00:00.000Z', 'Centre-Back', 'CB', 'Real Betis Balompié', 'La Liga', 0),
('dca343a8-12e5-53d6-89a8-916b120a5ee4', 8, 'Marcos', 'Javier', 'Acuña', '1991-10-28T00:00:00.000Z', 'Left-Back', 'LB', 'Sevilla FC', 'La Liga', 0),
('c62f2ac1-41e8-5d34-b073-2ba0913d0e31', 12, 'Gerónimo', NULL, 'Rulli', '1992-05-20T00:00:00.000Z', 'Goalkeeper', 'GK', 'Ajax Amsterdam', 'Eredivisie', 0),
('d3b0e8e8-2c34-531a-b608-b24fed0ef986', 14, 'Exequiel', 'Alejandro', 'Palacios', '1998-10-05T00:00:00.000Z', 'Central Midfield', 'CM', 'Bayer 04 Leverkusen', 'Bundesliga', 0),
('b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba', 15, 'Ángel', 'Martín', 'Correa', '1995-03-09T00:00:00.000Z', 'Right Winger', 'RW', 'Atlético Madrid', 'La Liga', 0),
('ecec27e8-487b-5622-b116-0855020477ed', 16, 'Thiago', 'Ezequiel', 'Almada', '2001-04-26T00:00:00.000Z', 'Attacking Midfield','AM', 'Atlanta United FC', 'Major League Soccer',0),
('7cc8d527-56a2-58bd-9528-2618fc139d30', 17, 'Alejandro', 'Darío', 'Gómez', '1988-02-15T00:00:00.000Z', 'Left Winger', 'LW', 'AC Monza', 'Serie A', 0),
('191c82af-0c51-526a-b903-c3600b61b506', 18, 'Guido', NULL, 'Rodríguez', '1994-04-12T00:00:00.000Z', 'Defensive Midfield','DM', 'Real Betis Balompié', 'La Liga', 0),
('7941cd7c-4df1-5952-97e8-1e7f5d08e8aa', 21, 'Paulo', 'Exequiel', 'Dybala', '1993-11-15T00:00:00.000Z', 'Second Striker', 'SS', 'AS Roma', 'Serie A', 0),
('79c96f29-c59f-5f98-96b8-3a5946246624', 22, 'Lautaro', 'Javier', 'Martínez', '1997-08-22T00:00:00.000Z', 'Centre-Forward', 'CF', 'Inter Milan', 'Serie A', 0),
('98306555-a466-5d18-804e-dc82175e697b', 25, 'Lisandro', NULL, 'Martínez', '1998-01-18T00:00:00.000Z', 'Centre-Back', 'CB', 'Manchester United', 'Premier League', 0);
4 changes: 4 additions & 0 deletions src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ spring.sql.init.data-locations=classpath:dml.sql
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true

# Flyway Configuration (disabled for tests)
# Tests use SQLite in-memory with Spring SQL init (ddl.sql + dml.sql) instead.
spring.flyway.enabled=false

# Server Configuration (disabled for tests)
server.port=0
management.server.port=0
2 changes: 1 addition & 1 deletion src/test/resources/ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ CREATE TABLE players (
abbrPosition TEXT NOT NULL,
team TEXT NOT NULL,
league TEXT NOT NULL,
starting11 INTEGER NOT NULL
starting11 BOOLEAN NOT NULL
);
Empty file added storage/.gitkeep
Empty file.
Binary file removed storage/players-sqlite3.db
Binary file not shown.
Loading