diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ed85209 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,9 @@ +POSTGRES_USER=cleat +POSTGRES_PASSWORD=password +POSTGRES_DB=cleat_db + +DB_USERNAME=cleat +DB_PASSWORD=password +DB_NAME=cleat_db +DB_HOST=localhost +REDIS_HOST=localhost diff --git a/backend/.gitignore b/backend/.gitignore index 0f76f6e..e2450b4 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -13,3 +13,5 @@ build/ # Local env *.local application-local.yml +.env + diff --git a/backend/README.md b/backend/README.md index 267e8eb..cad987b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -57,5 +57,34 @@ for example `dev.cleat.domain` and `dev.cleat.githubclient`. ./gradlew build # build and test everything ``` -Local dependencies (Postgres, Redis) are expected to run via Docker Compose. The -two services build into one container image each. +Local dependencies (Postgres, Redis) are expected to run via Docker Compose, while the API and Worker services are intended to be run locally via Gradle + + +# Docker Setup + +This project uses Docker Compose to orchestrate infrastructure services (PostgreSQL and Redis), while the API and Worker modules are managed via Gradle. + +## Prerequisites + +* Docker +* Docker Compose + +## Getting Started + +Navigate to the `backend` directory and use the following commands: + +### 1. Infrastructure (Docker) +Start the PostgreSQL and Redis containers: +`docker compose up -d` + +### 2. Running the Application (Gradle) +Use Gradle to run the modules: + +* For the API module: `./gradlew :apps:api:bootRun` +* For the Worker module: `./gradlew :apps:worker:bootRun` + +> **Note:** Running integration tests locally requires the Docker containers to be up and running. + +## Configuration + +Environment-specific configurations are managed within the `application.yml` files located in each module (api and worker). diff --git a/backend/apps/api/build.gradle.kts b/backend/apps/api/build.gradle.kts index 2a5efe5..2d4da1d 100644 --- a/backend/apps/api/build.gradle.kts +++ b/backend/apps/api/build.gradle.kts @@ -15,4 +15,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-security") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:postgresql") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.springframework.boot:spring-boot-testcontainers") } diff --git a/backend/apps/api/src/main/resources/application.yml b/backend/apps/api/src/main/resources/application.yml index 6bde21e..06bbefc 100644 --- a/backend/apps/api/src/main/resources/application.yml +++ b/backend/apps/api/src/main/resources/application.yml @@ -1,13 +1,16 @@ spring: application: name: cleat-api - autoconfigure: - exclude: - # Temporary: database is not wired yet. - # Remove these excludes once datasource is configured in issue #5. - - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - - org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:cleat_db} + username: ${DB_USERNAME:cleat} + password: ${DB_PASSWORD:password} + driver-class-name: org.postgresql.Driver + + data: + redis: + host: ${REDIS_HOST:localhost} + port: 6379 server: port: 8080 @@ -17,8 +20,12 @@ management: web: exposure: include: health,info + endpoint: + health: + probes: + enabled: true health: - defaults: + db: enabled: true redis: - enabled: false + enabled: true diff --git a/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java new file mode 100644 index 0000000..7af426b --- /dev/null +++ b/backend/apps/api/src/test/java/dev/cleat/api/AbstractIntegrationTest.java @@ -0,0 +1,21 @@ +package dev.cleat.api; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +@SpringBootTest +public abstract class AbstractIntegrationTest { + + @Container + @ServiceConnection + static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); + + @Container + @ServiceConnection + static GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); +} diff --git a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java index aa02220..eb41e9f 100644 --- a/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java +++ b/backend/apps/api/src/test/java/dev/cleat/api/CleatApiTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -public class CleatApiTests { +public class CleatApiTests extends AbstractIntegrationTest { @Test void contextLoad() {} diff --git a/backend/apps/worker/build.gradle.kts b/backend/apps/worker/build.gradle.kts index e2db9c0..4ae7e15 100644 --- a/backend/apps/worker/build.gradle.kts +++ b/backend/apps/worker/build.gradle.kts @@ -14,4 +14,8 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-actuator") testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:postgresql") + testImplementation("org.testcontainers:testcontainers") + testImplementation("org.springframework.boot:spring-boot-testcontainers") } diff --git a/backend/apps/worker/src/main/resources/application.yml b/backend/apps/worker/src/main/resources/application.yml index d519db1..ce6f2f6 100644 --- a/backend/apps/worker/src/main/resources/application.yml +++ b/backend/apps/worker/src/main/resources/application.yml @@ -1,11 +1,16 @@ spring: application: name: cleat-worker - # TODO: These excludes are temporary until the datasource lands in #5 - autoconfigure: - exclude: - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration + datasource: + url: jdbc:postgresql://${DB_HOST:localhost}:5432/${DB_NAME:cleat_db} + username: ${DB_USERNAME:cleat} + password: ${DB_PASSWORD:password} + driver-class-name: org.postgresql.Driver + + data: + redis: + host: ${REDIS_HOST:localhost} + port: 6379 server: port: 8081 @@ -18,4 +23,9 @@ management: endpoint: health: probes: - enabled: true \ No newline at end of file + enabled: true + health: + db: + enabled: true + redis: + enabled: true \ No newline at end of file diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java b/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java new file mode 100644 index 0000000..63842d6 --- /dev/null +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/AbstractIntegrationTest.java @@ -0,0 +1,21 @@ +package dev.cleat.worker; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Testcontainers +@SpringBootTest +public abstract class AbstractIntegrationTest { + + @Container + @ServiceConnection + static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine"); + + @Container + @ServiceConnection + static GenericContainer redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379); +} diff --git a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java index 45b7c09..7a6f1f4 100644 --- a/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java +++ b/backend/apps/worker/src/test/java/dev/cleat/worker/CleatWorkerApplicationTests.java @@ -4,7 +4,7 @@ import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -public class CleatWorkerApplicationTests { +public class CleatWorkerApplicationTests extends AbstractIntegrationTest { @Test void contextLoads() {} diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 9cacf33..a6c6495 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -32,6 +32,7 @@ subprojects { the().apply { imports { mavenBom(SpringBootPlugin.BOM_COORDINATES) + mavenBom("org.testcontainers:testcontainers-bom:1.20.1") } } diff --git a/backend/compose.yaml b/backend/compose.yaml new file mode 100644 index 0000000..680292c --- /dev/null +++ b/backend/compose.yaml @@ -0,0 +1,24 @@ +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_USER: ${POSTGRES_USER:-cleat} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_DB:-cleat_db} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cleat}"] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:7 + ports: + - "6379:6379" + +volumes: + postgres_data: \ No newline at end of file