diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..28ae17f --- /dev/null +++ b/.env.template @@ -0,0 +1,4 @@ +POSTGRES_USER=your_postgres_user +POSTGRES_PASSWORD=your_postgres_password +POSTGRES_DB=your_postgres_db +POSTGRES_PORT=5433 diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml index 2cea32a..0378ad9 100644 --- a/.github/workflows/sonar.yaml +++ b/.github/workflows/sonar.yaml @@ -40,4 +40,4 @@ jobs: mkdir -p src/test/resources echo ${{ secrets.APPLICATION_TEST_PROPERTIES }} | base64 -d > src/test/resources/application-test.properties echo "spring.sql.init.mode=never" >> src/test/resources/application-test.properties - mvn clean verify sonar:sonar -Dsonar.qualitygate.wait=true + mvn clean verify sonar:sonar -DskipDocker=true -Dsonar.qualitygate.wait=true diff --git a/.gitignore b/.gitignore index 0f24cd9..83d80fc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,7 @@ build/ ### VS Code ### .vscode/ +### Environment variables ### +.env # Test configuration generated in CI src/test/resources/application-test.properties diff --git a/README.md b/README.md index 6ad18d6..7e0284b 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,74 @@ # spring_boot_java_random_user -Spring Boot Java project that exposes a REST API consuming the public [Random User](https://randomuser.me/) API. -The interactive API documentation is available via **Swagger UI / OpenAPI**. +Spring Boot REST API that consumes the public [Random User](https://randomuser.me/) API and persists data in a PostgreSQL database. +Interactive API documentation is available via **Swagger UI / OpenAPI**. --- ## ๐Ÿ› ๏ธ Tech Stack -| Technology | Version | -|----------------------|-----------------| -| Java | 25 | -| Spring Boot | 4.0.3 | -| Spring Web MVC | (managed by Boot) | -| Spring Actuator | (managed by Boot) | -| springdoc-openapi | 3.0.1 | -| PostgreSQL (driver) | (managed by Boot) | -| Testcontainers | (managed by Boot) | -| Maven | Wrapper included | +| Technology | Version | +|---|---| +| Java | 25 | +| Spring Boot | 4.0.3 | +| Spring Web MVC | (managed by Boot) | +| Spring Data JDBC | (managed by Boot) | +| Spring Actuator | (managed by Boot) | +| springdoc-openapi | 3.0.1 | +| PostgreSQL | 15 | +| dotenv-java | 5.2.2 | +| Docker / Docker Compose | - | +| Maven | Wrapper included | --- ## ๐Ÿ“‹ Prerequisites -- **Java 25** (or compatible version) +- **Java 25+** - **Maven** (or use the included `./mvnw` wrapper) -- **PostgreSQL** running (if data persistence is enabled) +- **Docker Desktop** --- ## โš™๏ธ Configuration -The main configuration file is located at: +### 1. Environment variables -``` -src/main/resources/application.properties +Copy the template and fill in the values: + +```bash +cp .env.template .env ``` -### Properties to configure +`.env` content: + +```env +POSTGRES_USER={POSTGRES_USER} +POSTGRES_PASSWORD={POSTGRES_PASSWORD} +POSTGRES_DB={POSTGRES_DB} +POSTGRES_PORT={POSTGRES_PORT} +``` -```properties -spring.application.name=spring_boot_java_random_user +> โš ๏ธ The `.env` file is git-ignored. Never commit it. -# Swagger UI โ€” available at /api -springdoc.swagger-ui.path=/api +### 2. Test properties -# PostgreSQL datasource (add these if you use persistence) -spring.datasource.url=jdbc:postgresql://localhost:5432/ -spring.datasource.username= -spring.datasource.password= -spring.datasource.driver-class-name=org.postgresql.Driver +```bash +cp src/test/resources/application-test.properties.template src/test/resources/application-test.properties ``` -> โš ๏ธ **Common startup error**: -> ``` -> Failed to configure a DataSource: 'url' attribute is not specified -> and no embedded datasource could be configured. -> Reason: Failed to determine a suitable driver class -> ``` -> This error occurs because the PostgreSQL driver is present in the dependencies but the datasource URL is not configured. -> **Fix**: either add the `spring.datasource.*` properties above, or exclude the DataSource auto-configuration if no database is needed: -> ```properties -> spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration -> ``` +> โš ๏ธ This file is also git-ignored. --- ## ๐Ÿš€ Running the project +### Start the database + +```bash +docker-compose up -d +``` + ### With the Maven Wrapper (recommended) ```bash @@ -86,17 +88,17 @@ mvn spring-boot:run java -jar target/spring_boot_java_random_user-0.0.1-SNAPSHOT.jar ``` +The application will be available at: http://localhost:8080 + --- ## ๐Ÿ“– API Documentation (Swagger UI) -Once the project is running, the interactive documentation is available at: - ``` http://localhost:8080/api ``` -The raw OpenAPI specification (JSON) is available at: +Raw OpenAPI specification (JSON): ``` http://localhost:8080/v3/api-docs @@ -106,8 +108,6 @@ http://localhost:8080/v3/api-docs ## ๐Ÿ” Monitoring (Actuator) -Spring Boot Actuator is enabled. Health endpoints are available at: - ``` http://localhost:8080/actuator http://localhost:8080/actuator/health @@ -117,25 +117,24 @@ http://localhost:8080/actuator/health ## ๐Ÿงช Tests -Run unit and integration tests: +The Maven plugin starts and stops Docker Compose automatically during tests: ```bash -./mvnw test +./mvnw verify ``` -Tests use **Testcontainers** to spin up ephemeral Docker containers for external dependencies (e.g. PostgreSQL). +Execution cycle: +1. `pre-integration-test` โ†’ `docker-compose up` (PostgreSQL starts) +2. `integration-test` โ†’ tests run +3. `post-integration-test` โ†’ `docker-compose down` (PostgreSQL stops) -> **Prerequisite for tests**: Docker must be installed and running. +> **Prerequisite**: Docker must be installed and running. --- ## ๐Ÿ”Ž Code Quality (SonarQube) -A GitHub Actions workflow is configured in: - -```bash -.github/workflows/sonar.yaml -``` +A GitHub Actions workflow is configured in `.github/workflows/sonar.yaml`. ### Workflow triggers @@ -147,45 +146,32 @@ A GitHub Actions workflow is configured in: - Java 25 setup (Temurin) - Maven build + tests + SonarQube analysis: -```bash -mvn clean verify sonar:sonar -``` - -The CI command waits for the Quality Gate result: - ```bash ./mvnw clean verify sonar:sonar -Dsonar.qualitygate.wait=true ``` -Before running tests, the workflow recreates: - -```bash -src/test/resources/application-test.properties -``` - -from a Base64-encoded GitHub secret. - ### Required GitHub Secrets -- `SONAR_TOKEN` -- `SONAR_HOST_URL` -- `APPLICATION_TEST_PROPERTIES` (Base64-encoded `application-test.properties` content) +| Secret | Description | +| `APPLICATION_TEST_PROPERTIES` (Base64-encoded `application-test.properties` content) +| `SONAR_TOKEN` | SonarQube authentication token | +| `SONAR_HOST_URL` | SonarQube instance URL | +| `POSTGRES_USER` | PostgreSQL user | +| `POSTGRES_PASSWORD` | PostgreSQL password | +| `POSTGRES_DB` | Database name | +| `POSTGRES_PORT` | PostgreSQL port | > `GITHUB_TOKEN` is provided automatically by GitHub Actions. -### Generate the JaCoCo coverage report locally - -Run: +### JaCoCo coverage report (local) ```bash ./mvnw clean verify ``` Generated reports: - -- HTML report: `target/site/jacoco/index.html` -- XML report (used by SonarQube): `target/site/jacoco/jacoco.xml` - +- HTML: `target/site/jacoco/index.html` +- XML (used by SonarQube): `target/site/jacoco/jacoco.xml` --- @@ -195,16 +181,43 @@ Generated reports: src/ โ”œโ”€โ”€ main/ โ”‚ โ”œโ”€โ”€ java/com/xpeho/spring_boot_java_random_user/ -โ”‚ โ”‚ โ””โ”€โ”€ SpringBootJavaRandomUserApplication.java # Entry point +โ”‚ โ”‚ โ””โ”€โ”€ SpringBootJavaRandomUserApplication.java โ† Entry point, loads .env โ”‚ โ””โ”€โ”€ resources/ -โ”‚ โ””โ”€โ”€ application.properties # Configuration +โ”‚ โ”œโ”€โ”€ application.properties โ† Spring configuration +โ”‚ โ””โ”€โ”€ schema.sql โ† Database schema โ””โ”€โ”€ test/ - โ””โ”€โ”€ java/com/xpeho/spring_boot_java_random_user/ - โ””โ”€โ”€ SpringBootJavaRandomUserApplicationTests.java + โ”œโ”€โ”€ java/com/xpeho/spring_boot_java_random_user/ + โ”‚ โ””โ”€โ”€ SpringBootJavaRandomUserApplicationTests.java + โ””โ”€โ”€ resources/ + โ”œโ”€โ”€ application-test.properties โ† Test config (git-ignored) + โ””โ”€โ”€ application-test.properties.template โ† Template to copy +.env โ† Environment variables (git-ignored) +.env.template โ† Template to copy +docker-compose.yml โ† Local PostgreSQL ``` --- +## ๐Ÿ—„๏ธ Database + +### Schema + +The `user` table is created automatically on startup via `schema.sql`: + +| Column | Type | Description | +|---|---|---| +| `id` | SERIAL PK | Auto-incremented identifier | +| `gender` | VARCHAR(20) | Gender | +| `firstname` | VARCHAR(100) | First name (`name.first`) | +| `lastname` | VARCHAR(100) | Last name (`name.last`) | +| `civility` | VARCHAR(20) | Title (`name.title`) | +| `email` | VARCHAR(255) | Email address | +| `phone` | VARCHAR(50) | Phone number | +| `picture` | VARCHAR(500) | Medium picture URL | +| `nationality` | VARCHAR(10) | Nationality | + +--- + ## ๐ŸŒ External API This project consumes the public **Random User Generator** API: @@ -217,12 +230,12 @@ This project consumes the public **Random User Generator** API: ## โœ… Todo - [x] [Add Sonarqube in the project](https://github.com/XPEHO/spring_boot_java_random_user/issues/2) -- [ ] [Add PostgreSQL database with docker](https://github.com/XPEHO/spring_boot_java_random_user/issues/6) -- [ ] [Add this endpoint get /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5) -- [ ] [Add this endpoint get /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8) -- [ ] [Add this endpoint put /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9) -- [ ] [Add this endpoint delete /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10) -- [ ] [Add this endpoint post /user](https://github.com/XPEHO/spring_boot_java_random_user/issues/11) +- [x] [Add PostgreSQL database with docker](https://github.com/XPEHO/spring_boot_java_random_user/issues/6) +- [ ] [Add this endpoint GET /user/random](https://github.com/XPEHO/spring_boot_java_random_user/issues/5) +- [ ] [Add this endpoint GET /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/8) +- [ ] [Add this endpoint PUT /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/9) +- [ ] [Add this endpoint DELETE /user/{id}](https://github.com/XPEHO/spring_boot_java_random_user/issues/10) +- [ ] [Add this endpoint POST /user](https://github.com/XPEHO/spring_boot_java_random_user/issues/11) --- diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be3b317 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: '3.8' + +services: + postgres: + image: postgres:17.9-alpine + container_name: xpeho_postgres + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "${POSTGRES_PORT}:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - xpeho_network + +volumes: + postgres_data: + driver: local + +networks: + xpeho_network: + driver: bridge + diff --git a/pom.xml b/pom.xml index eddcf54..20b9212 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 25 + false **/*Application.java ${project.build.directory}/site/jacoco/jacoco.xml @@ -41,10 +42,10 @@ org.springframework.boot spring-boot-starter-actuator - - - - + + org.springframework.boot + spring-boot-starter-data-jdbc + org.springframework.boot spring-boot-starter-webmvc @@ -61,16 +62,23 @@ postgresql runtime + + + io.github.cdimascio + java-dotenv + 5.2.2 + + org.springframework.boot spring-boot-starter-actuator-test test - - - - - + + org.springframework.boot + spring-boot-starter-data-jdbc-test + test + org.springframework.boot spring-boot-starter-webmvc-test @@ -99,6 +107,67 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/*Tests.java + **/*Test.java + + + + + + integration-test + verify + + + + + + com.dkanejs.maven.plugins + docker-compose-maven-plugin + 4.0.0 + + ${skipDocker} + ${project.basedir}/docker-compose.yml + true + false + true + + + + + docker-compose-down-before + pre-integration-test + + down + + + + docker-compose-up + pre-integration-test + + up + + + + docker-compose-down + post-integration-test + + down + + + + org.jacoco jacoco-maven-plugin diff --git a/src/main/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplication.java b/src/main/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplication.java index 97546ad..b7c997b 100644 --- a/src/main/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplication.java +++ b/src/main/java/com/xpeho/spring_boot_java_random_user/SpringBootJavaRandomUserApplication.java @@ -1,5 +1,6 @@ package com.xpeho.spring_boot_java_random_user; +import io.github.cdimascio.dotenv.Dotenv; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -7,6 +8,12 @@ public class SpringBootJavaRandomUserApplication { public static void main(String[] args) { + Dotenv.configure() + .ignoreIfMissing() + .load() + .entries() + .forEach(entry -> System.setProperty(entry.getKey(), entry.getValue())); + SpringApplication.run(SpringBootJavaRandomUserApplication.class, args); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 16574d6..df2d4f1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,18 @@ spring.application.name=spring_boot_java_random_user + # Swagger UI custom path springdoc.swagger-ui.path=/api + +# Database configuration +spring.datasource.url=jdbc:postgresql://localhost:${POSTGRES_PORT}/${POSTGRES_DB} +spring.datasource.username=${POSTGRES_USER} +spring.datasource.password=${POSTGRES_PASSWORD} +spring.datasource.driver-class-name=org.postgresql.Driver + + +# Spring Data JDBC - Initialize database +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql +spring.sql.init.data-locations= +spring.sql.init.platform=postgresql +spring.sql.init.continue-on-error=true diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..2140b5c --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS "user"; + +CREATE TABLE IF NOT EXISTS "user" +( + id SERIAL PRIMARY KEY, + gender VARCHAR(20), + firstname VARCHAR(100), + lastname VARCHAR(100), + civility VARCHAR(20), + email VARCHAR(255), + phone VARCHAR(50), + picture VARCHAR(500), + nationality VARCHAR(10) +); diff --git a/src/test/resources/application-test.properties.template b/src/test/resources/application-test.properties.template new file mode 100644 index 0000000..7b598cc --- /dev/null +++ b/src/test/resources/application-test.properties.template @@ -0,0 +1,6 @@ +POSTGRES_USER=your_postgres_user +POSTGRES_PASSWORD=your_postgres_password +POSTGRES_DB=your_postgres_db +POSTGRES_PORT=5433 + +