T-4: add JPA, Liquibase, Testcontainers, and deployment docs
Set up development infrastructure for TDD: JPA + Liquibase for database migrations, Testcontainers for integration tests against real PostgreSQL, profile-based configuration (prod/local), and README deployment documentation with docker-compose example. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -49,6 +49,9 @@ npm-debug.log*
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Spring Boot local profile (developer-specific, not committed)
|
||||
backend/src/main/resources/application-local.properties
|
||||
|
||||
# Editor swap files
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
@@ -21,6 +21,7 @@ FROM eclipse-temurin:25-jre-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=backend-build /app/backend/target/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
|
||||
67
README.md
67
README.md
@@ -47,6 +47,7 @@ A privacy-focused, self-hostable web app for event announcements and RSVPs. An a
|
||||
|
||||
- Java (latest LTS) + Maven wrapper (`./mvnw`, included)
|
||||
- Node.js (latest LTS) + npm
|
||||
- Docker (for running backend tests via Testcontainers)
|
||||
|
||||
### Project structure
|
||||
|
||||
@@ -68,6 +69,26 @@ cd backend && ./mvnw test
|
||||
cd frontend && npm run test:unit
|
||||
```
|
||||
|
||||
### Running the backend locally
|
||||
|
||||
**Option A: Without external PostgreSQL (Testcontainers)**
|
||||
|
||||
```bash
|
||||
cd backend && ./mvnw spring-boot:test-run
|
||||
```
|
||||
|
||||
Starts the app with a Testcontainers-managed PostgreSQL that is created and destroyed automatically. Requires Docker.
|
||||
|
||||
**Option B: With external PostgreSQL**
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
cp src/main/resources/application-local.properties.example \
|
||||
src/main/resources/application-local.properties
|
||||
# Edit application-local.properties if your PostgreSQL uses different credentials
|
||||
./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
@@ -129,6 +150,52 @@ ArchUnit enforces hexagonal boundaries: domain must not depend on adapters, appl
|
||||
|---------------------|------------------|-------------------|
|
||||
| Prettier | `npm run format` | Formatting issues |
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker Compose
|
||||
|
||||
The app ships as a single Docker image. It requires an external PostgreSQL database.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: fete
|
||||
POSTGRES_USER: fete
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- fete-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fete"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
image: git.bahamut.nitrix.one/nitrix/fete:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DATABASE_URL: jdbc:postgresql://db:5432/fete
|
||||
DATABASE_USERNAME: fete
|
||||
DATABASE_PASSWORD: changeme
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
fete-db:
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|---------------------|----------|---------|-----------------------------------|
|
||||
| `DATABASE_URL` | Yes | — | JDBC connection string |
|
||||
| `DATABASE_USERNAME` | Yes | — | Database username |
|
||||
| `DATABASE_PASSWORD` | Yes | — | Database password |
|
||||
|
||||
## License
|
||||
|
||||
GPL — see [LICENSE](LICENSE) for details.
|
||||
|
||||
@@ -37,6 +37,22 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
@@ -49,6 +65,24 @@
|
||||
<version>1.4.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Local development database
|
||||
# Copy this file to application-local.properties and adjust as needed.
|
||||
# Start with: ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
|
||||
spring.datasource.username=fete
|
||||
spring.datasource.password=fete
|
||||
4
backend/src/main/resources/application-prod.properties
Normal file
4
backend/src/main/resources/application-prod.properties
Normal file
@@ -0,0 +1,4 @@
|
||||
# Database (required)
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
@@ -1,4 +1,12 @@
|
||||
spring.application.name=fete
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.open-in-view=false
|
||||
|
||||
# Liquibase
|
||||
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
|
||||
12
backend/src/main/resources/db/changelog/000-baseline.xml
Normal file
12
backend/src/main/resources/db/changelog/000-baseline.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="000-baseline" author="nitrix">
|
||||
<comment>Baseline changeset — Liquibase tooling verification</comment>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<include file="db/changelog/000-baseline.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -8,10 +8,12 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestcontainersConfig.class)
|
||||
class FeteApplicationTest {
|
||||
|
||||
@Autowired
|
||||
|
||||
14
backend/src/test/java/de/fete/TestFeteApplication.java
Normal file
14
backend/src/test/java/de/fete/TestFeteApplication.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/** Test entry point — starts the app with Testcontainers PostgreSQL. */
|
||||
public class TestFeteApplication {
|
||||
|
||||
/** Starts the application with Testcontainers PostgreSQL. */
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.from(FeteApplication::main)
|
||||
.with(TestcontainersConfig.class)
|
||||
.run(args);
|
||||
}
|
||||
}
|
||||
17
backend/src/test/java/de/fete/TestcontainersConfig.java
Normal file
17
backend/src/test/java/de/fete/TestcontainersConfig.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/** Provides a Testcontainers PostgreSQL instance for integration tests. */
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
PostgreSQLContainer<?> postgresContainer() {
|
||||
return new PostgreSQLContainer<>("postgres:17-alpine");
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,17 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import de.fete.TestcontainersConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestcontainersConfig.class)
|
||||
class WebConfigTest {
|
||||
|
||||
@Autowired
|
||||
|
||||
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
567
docs/agents/plan/2026-03-04-t4-development-infrastructure.md
Normal file
@@ -0,0 +1,567 @@
|
||||
---
|
||||
date: 2026-03-04T20:09:31.044992+00:00
|
||||
git_commit: cb0bcad145b03fec63be0ee3c1fca46ee545329e
|
||||
branch: master
|
||||
topic: "T-4: Development Infrastructure Setup"
|
||||
tags: [plan, database, liquibase, testcontainers, configuration, docker-compose]
|
||||
status: draft
|
||||
---
|
||||
|
||||
# T-4: Development Infrastructure Setup — Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
Set up the remaining development infrastructure needed before the first user story (US-1) can be implemented with TDD. This adds JPA + Liquibase for database migrations, PostgreSQL connectivity via environment variables, Testcontainers for integration tests, app-specific configuration properties, and README deployment documentation with a docker-compose example.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
**Already complete (no work needed):**
|
||||
- SPA router: Vue Router with `createWebHistory`, backend SPA fallback in `WebConfig.java`
|
||||
- Frontend test infrastructure: Vitest + `@vue/test-utils`, sample test passing
|
||||
- Both test suites executable: `./mvnw test` (3 tests) and `npm run test:unit` (1 test)
|
||||
|
||||
**Missing (all work in this plan):**
|
||||
- JPA, Liquibase, PostgreSQL driver — no database dependencies in `pom.xml`
|
||||
- Testcontainers — not configured
|
||||
- Database connectivity — no datasource properties
|
||||
- App-specific config — no `@ConfigurationProperties`
|
||||
- Profile separation — no `application-prod.properties`
|
||||
- Deployment docs — no docker-compose in README
|
||||
|
||||
### Key Discoveries:
|
||||
- `backend/pom.xml:1-170` — Spring Boot 3.5.11, no DB dependencies
|
||||
- `backend/src/main/resources/application.properties:1-4` — Only app name + actuator
|
||||
- `HexagonalArchitectureTest.java:22` — `config` is already an adapter in ArchUnit rules
|
||||
- `FeteApplicationTest.java` — Uses `@SpringBootTest` + MockMvc; will need datasource after JPA is added
|
||||
- `Dockerfile:26` — No `SPRING_PROFILES_ACTIVE` set
|
||||
- `.gitignore:47-51` — `.env*` patterns exist but no `application-local.properties`
|
||||
|
||||
## Desired End State
|
||||
|
||||
After this plan is complete:
|
||||
- `./mvnw test` runs all backend tests (including new Testcontainers-backed integration tests) against a real PostgreSQL without external setup
|
||||
- `./mvnw spring-boot:run -Dspring-boot.run.profiles=local` starts the app against a local PostgreSQL
|
||||
- Docker container starts with `DATABASE_URL`/`DATABASE_USERNAME`/`DATABASE_PASSWORD` env vars, runs Liquibase migrations, and responds to health checks
|
||||
- README contains a copy-paste-ready docker-compose example for deployment
|
||||
- `FeteProperties` scaffolds `fete.unsplash.api-key` and `fete.max-active-events` (no business logic yet)
|
||||
|
||||
### Verification:
|
||||
- `cd backend && ./mvnw verify` — all tests green, checkstyle + spotbugs pass
|
||||
- `cd frontend && npm run test:unit -- --run` — unchanged, still green
|
||||
- `docker build .` — succeeds
|
||||
- docker-compose (app + postgres) — container starts, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
## What We're NOT Doing
|
||||
|
||||
- No JPA entities or repositories — those come with US-1
|
||||
- No domain model classes — those come with US-1
|
||||
- No business logic for `FeteProperties` (Unsplash, max events) — US-13/US-16
|
||||
- No standalone `docker-compose.yml` file in repo — inline in README per CLAUDE.md
|
||||
- No `application-local.properties` committed — only the `.example` template
|
||||
- No changes to frontend code — AC 4/6/7 are already met
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: JPA + Liquibase + PostgreSQL Dependencies
|
||||
|
||||
### Overview
|
||||
Add all database-related dependencies to `pom.xml`, create the Liquibase changelog structure with an empty baseline changeset, and update `application.properties` with JPA and Liquibase settings.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add database dependencies to `pom.xml`
|
||||
**File**: `backend/pom.xml`
|
||||
**Changes**: Add four dependencies after the existing `spring-boot-starter-validation` block.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Add Testcontainers dependencies in test scope (after `archunit-junit5`):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Spring Boot's dependency management handles versions for all of these — no explicit version tags needed (except `archunit-junit5` which is already versioned).
|
||||
|
||||
#### [x] 2. Create Liquibase master changelog
|
||||
**File**: `backend/src/main/resources/db/changelog/db.changelog-master.xml` (new)
|
||||
**Changes**: Create the master changelog that includes individual changesets.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<include file="db/changelog/000-baseline.xml"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 3. Create empty baseline changeset
|
||||
**File**: `backend/src/main/resources/db/changelog/000-baseline.xml` (new)
|
||||
**Changes**: Empty changeset that proves the tooling works. Liquibase creates its tracking tables (`databasechangelog`, `databasechangeloglock`) automatically.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<!-- T-4: Baseline changeset. Proves Liquibase tooling works.
|
||||
First real schema comes with US-1. -->
|
||||
<changeSet id="000-baseline" author="fete">
|
||||
<comment>Baseline changeset — Liquibase tooling verification</comment>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
```
|
||||
|
||||
#### [x] 4. Update application.properties with JPA and Liquibase settings
|
||||
**File**: `backend/src/main/resources/application.properties`
|
||||
**Changes**: Add JPA and Liquibase configuration (environment-independent, always active).
|
||||
|
||||
```properties
|
||||
spring.application.name=fete
|
||||
|
||||
# JPA
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.open-in-view=false
|
||||
|
||||
# Liquibase
|
||||
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (dependencies resolve, checkstyle passes)
|
||||
- [ ] Changelog XML files are well-formed (Maven compile does not fail on resource processing)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] Verify `pom.xml` has all six new dependencies with correct scopes
|
||||
- [ ] Verify changelog directory structure: `db/changelog/db.changelog-master.xml` includes `000-baseline.xml`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Profile-Based Configuration and App Properties
|
||||
|
||||
### Overview
|
||||
Create the profile-based property files for production and local development, add the `FeteProperties` configuration class, update `.gitignore`, and set the production profile in the Dockerfile.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create production properties file
|
||||
**File**: `backend/src/main/resources/application-prod.properties` (new)
|
||||
**Changes**: Production profile with environment variable placeholders. Activated in Docker via `SPRING_PROFILES_ACTIVE=prod`.
|
||||
|
||||
```properties
|
||||
# Database (required)
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
|
||||
# App-specific (optional)
|
||||
fete.unsplash.api-key=${UNSPLASH_API_KEY:}
|
||||
fete.max-active-events=${MAX_ACTIVE_EVENTS:0}
|
||||
```
|
||||
|
||||
#### [x] 2. Create local development properties template
|
||||
**File**: `backend/src/main/resources/application-local.properties.example` (new)
|
||||
**Changes**: Template that developers copy to `application-local.properties` (which is gitignored).
|
||||
|
||||
```properties
|
||||
# Local development database
|
||||
# Copy this file to application-local.properties and adjust as needed.
|
||||
# Start with: ./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
|
||||
spring.datasource.username=fete
|
||||
spring.datasource.password=fete
|
||||
```
|
||||
|
||||
#### [x] 3. Add `application-local.properties` to `.gitignore`
|
||||
**File**: `.gitignore`
|
||||
**Changes**: Add the gitignore entry for the local properties file (under the Environment section).
|
||||
|
||||
```
|
||||
# Spring Boot local profile (developer-specific, not committed)
|
||||
backend/src/main/resources/application-local.properties
|
||||
```
|
||||
|
||||
#### ~~4. Create `FeteProperties` configuration properties class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FeteProperties.java` (new)
|
||||
**Changes**: Type-safe configuration for app-specific settings. Both properties are only scaffolded — business logic comes with US-13/US-16.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Application-specific configuration properties.
|
||||
*
|
||||
* <p>Mapped from {@code fete.*} properties. Both properties are optional:
|
||||
* <ul>
|
||||
* <li>{@code fete.unsplash.api-key} — Unsplash API key (empty = feature disabled)
|
||||
* <li>{@code fete.max-active-events} — Maximum active events (0 = unlimited)
|
||||
* </ul>
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "fete")
|
||||
public class FeteProperties {
|
||||
|
||||
private final Unsplash unsplash;
|
||||
private final int maxActiveEvents;
|
||||
|
||||
/** Creates FeteProperties with the given values. */
|
||||
public FeteProperties(Unsplash unsplash, int maxActiveEvents) {
|
||||
this.unsplash = unsplash != null ? unsplash : new Unsplash("");
|
||||
this.maxActiveEvents = maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Returns the Unsplash configuration. */
|
||||
public Unsplash getUnsplash() {
|
||||
return unsplash;
|
||||
}
|
||||
|
||||
/** Returns the maximum number of active events (0 = unlimited). */
|
||||
public int getMaxActiveEvents() {
|
||||
return maxActiveEvents;
|
||||
}
|
||||
|
||||
/** Unsplash-related configuration. */
|
||||
public record Unsplash(String apiKey) {
|
||||
|
||||
/** Creates Unsplash config with the given API key. */
|
||||
public Unsplash {
|
||||
if (apiKey == null) {
|
||||
apiKey = "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if an API key is configured. */
|
||||
public boolean isEnabled() {
|
||||
return !apiKey.isBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ~~5. Create `FetePropertiesConfig` configuration class~~ (deferred)
|
||||
**File**: `backend/src/main/java/de/fete/config/FetePropertiesConfig.java` (new)
|
||||
**Changes**: Separate `@Configuration` that enables `FeteProperties`.
|
||||
|
||||
```java
|
||||
package de.fete.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Activates {@link FeteProperties} binding. */
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(FeteProperties.class)
|
||||
public class FetePropertiesConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 6. Set production profile in Dockerfile
|
||||
**File**: `Dockerfile`
|
||||
**Changes**: Add `ENV SPRING_PROFILES_ACTIVE=prod` in the runtime stage, before `ENTRYPOINT`.
|
||||
|
||||
```dockerfile
|
||||
# Stage 3: Runtime
|
||||
FROM eclipse-temurin:25-jre-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=backend-build /app/backend/target/*.jar app.jar
|
||||
EXPOSE 8080
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw compile` succeeds (FeteProperties compiles, checkstyle passes)
|
||||
- [ ] `docker build .` succeeds
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `application-prod.properties` contains all five env-var placeholders
|
||||
- [ ] `application-local.properties.example` is committed; `application-local.properties` is gitignored
|
||||
- [ ] `FeteProperties` fields: `unsplash.apiKey` (String), `maxActiveEvents` (int)
|
||||
- [ ] Dockerfile has `ENV SPRING_PROFILES_ACTIVE=prod` before `ENTRYPOINT`
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Testcontainers Integration
|
||||
|
||||
### Overview
|
||||
Set up the TestApplication pattern so that all `@SpringBootTest` tests automatically get a Testcontainers-managed PostgreSQL instance. This is critical: once JPA is on the classpath, every `@SpringBootTest` needs a datasource. Without this, all three existing `@SpringBootTest` tests break.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Create Testcontainers configuration
|
||||
**File**: `backend/src/test/java/de/fete/TestcontainersConfig.java` (new)
|
||||
**Changes**: Registers a PostgreSQL Testcontainer with `@ServiceConnection` for automatic datasource wiring.
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/** Provides a Testcontainers PostgreSQL instance for integration tests. */
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfig {
|
||||
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
PostgreSQLContainer<?> postgresContainer() {
|
||||
return new PostgreSQLContainer<>("postgres:17-alpine");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 2. Create TestFeteApplication for `spring-boot:test-run`
|
||||
**File**: `backend/src/test/java/de/fete/TestFeteApplication.java` (new)
|
||||
**Changes**: Entry point that imports `TestcontainersConfig`. Enables `./mvnw spring-boot:test-run` for local development with Testcontainers (no external PostgreSQL needed).
|
||||
|
||||
```java
|
||||
package de.fete;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/** Test entry point — starts the app with Testcontainers PostgreSQL. */
|
||||
public class TestFeteApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.from(FeteApplication::main)
|
||||
.with(TestcontainersConfig.class)
|
||||
.run(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### [x] 3. Import TestcontainersConfig in existing `@SpringBootTest` tests
|
||||
**File**: `backend/src/test/java/de/fete/FeteApplicationTest.java`
|
||||
**Changes**: Add `@Import(TestcontainersConfig.class)` so the test gets a datasource.
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Import(TestcontainersConfig.class)
|
||||
class FeteApplicationTest {
|
||||
// ... existing tests unchanged
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `backend/src/test/java/de/fete/config/WebConfigTest.java`
|
||||
**Changes**: Same — add `@Import(TestcontainersConfig.class)`.
|
||||
|
||||
Note: `HexagonalArchitectureTest` uses `@AnalyzeClasses` (ArchUnit), not `@SpringBootTest` — it needs no changes.
|
||||
|
||||
#### [x] 4. Add SpotBugs exclusion for Testcontainers resource management
|
||||
**File**: `backend/spotbugs-exclude.xml`
|
||||
**Changes**: Testcontainers `PostgreSQLContainer` bean intentionally has container lifecycle managed by Spring, not try-with-resources. SpotBugs may flag this. Add exclusion if needed — check after running `./mvnw verify`.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd backend && ./mvnw test` — all existing tests pass (context loads, health endpoint, ArchUnit)
|
||||
- [ ] `cd backend && ./mvnw verify` — full verify including SpotBugs passes
|
||||
- [ ] Testcontainers starts a PostgreSQL container during test execution (visible in test output)
|
||||
- [ ] Liquibase baseline migration runs against Testcontainers PostgreSQL
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] `./mvnw spring-boot:test-run` starts the app with a Testcontainers PostgreSQL (for local dev without external DB)
|
||||
|
||||
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation from the human before proceeding to the next phase.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: README Deployment Documentation
|
||||
|
||||
### Overview
|
||||
Add a deployment section to the README with a docker-compose example, environment variable documentation, and local development setup instructions.
|
||||
|
||||
### Changes Required:
|
||||
|
||||
#### [x] 1. Add deployment section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Add a `## Deployment` section after the existing `## Code quality` section and before `## License`. Contains the docker-compose example, environment variable table, and notes.
|
||||
|
||||
```markdown
|
||||
## Deployment
|
||||
|
||||
### Docker Compose
|
||||
|
||||
The app ships as a single Docker image. It requires an external PostgreSQL database.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: fete
|
||||
POSTGRES_USER: fete
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- fete-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fete"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
image: gitea.example.com/user/fete:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DATABASE_URL: jdbc:postgresql://db:5432/fete
|
||||
DATABASE_USERNAME: fete
|
||||
DATABASE_PASSWORD: changeme
|
||||
# MAX_ACTIVE_EVENTS: 100
|
||||
# UNSPLASH_API_KEY: your-key-here
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
fete-db:
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------------------|----------|-----------|------------------------------------------------|
|
||||
| `DATABASE_URL` | Yes | — | JDBC connection string for PostgreSQL |
|
||||
| `DATABASE_USERNAME` | Yes | — | Database username |
|
||||
| `DATABASE_PASSWORD` | Yes | — | Database password |
|
||||
| `MAX_ACTIVE_EVENTS` | No | Unlimited | Maximum number of simultaneously active events |
|
||||
| `UNSPLASH_API_KEY` | No | — | Unsplash API key for header image search |
|
||||
```
|
||||
|
||||
#### [x] 2. Add local development setup section to README
|
||||
**File**: `README.md`
|
||||
**Changes**: Extend the `## Development` section with database setup instructions.
|
||||
|
||||
```markdown
|
||||
### Local database setup
|
||||
|
||||
**Option A: Testcontainers (no external PostgreSQL needed)**
|
||||
|
||||
```bash
|
||||
cd backend && ./mvnw spring-boot:test-run
|
||||
```
|
||||
|
||||
This starts the app with a Testcontainers-managed PostgreSQL that is created and destroyed automatically.
|
||||
|
||||
**Option B: External PostgreSQL**
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
cp src/main/resources/application-local.properties.example \
|
||||
src/main/resources/application-local.properties
|
||||
# Edit application-local.properties if your PostgreSQL uses different credentials
|
||||
./mvnw spring-boot:run -Dspring-boot.run.profiles=local
|
||||
```
|
||||
```
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
- [ ] `cd frontend && npm run test:unit -- --run` — frontend tests still pass (no regression)
|
||||
|
||||
#### Manual Verification:
|
||||
- [ ] README docker-compose example is syntactically correct YAML
|
||||
- [ ] Environment variable table lists all five variables with correct Required/Default values
|
||||
- [ ] Local development section documents both Testcontainers and external PostgreSQL options
|
||||
- [ ] docker-compose startup: `docker compose up` starts app + postgres, `/actuator/health` returns `{"status":"UP"}`
|
||||
|
||||
**Implementation Note**: After completing this phase, all T-4 acceptance criteria should be met. Run the full verification checklist below.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests:
|
||||
- `FeteProperties` — verify defaults (empty API key = disabled, maxActiveEvents=0 = unlimited)
|
||||
- No other new unit tests in T-4 — the infrastructure is verified by integration tests
|
||||
|
||||
### Integration Tests:
|
||||
- Existing `FeteApplicationTest.contextLoads()` — validates that Spring context starts with JPA + Liquibase + Testcontainers
|
||||
- Existing `FeteApplicationTest.healthEndpointReturns200()` — validates health check includes DB health
|
||||
- Existing `WebConfigTest` — validates SPA routing still works with JPA on classpath
|
||||
- ArchUnit rules — validate `FeteProperties`/`FetePropertiesConfig` in `config` adapter is properly isolated
|
||||
|
||||
### Manual Testing Steps:
|
||||
1. `cd backend && ./mvnw verify` — full backend pipeline green
|
||||
2. `cd frontend && npm run test:unit -- --run` — frontend unchanged
|
||||
3. `docker build .` — image builds successfully
|
||||
4. docker-compose (app + postgres) — start, wait for health, verify `/actuator/health` returns UP
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Testcontainers PostgreSQL startup adds ~3-5 seconds to backend test execution. This is acceptable for integration tests.
|
||||
- Testcontainers reuses the container across all `@SpringBootTest` classes in a single Maven run (Spring's test context caching).
|
||||
- The empty baseline changeset adds negligible startup time.
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- **Existing tests**: `FeteApplicationTest` and `WebConfigTest` need `@Import(TestcontainersConfig.class)` — without it, they fail because JPA requires a datasource.
|
||||
- **CI pipeline**: `./mvnw -B verify` now requires Docker for Testcontainers. Gitea Actions `ubuntu-latest` runners have Docker available. If the runner uses Docker-in-Docker, `DOCKER_HOST` may need configuration — verify after implementation.
|
||||
- **Local development**: Developers now need either Docker (for Testcontainers via `./mvnw spring-boot:test-run`) or a local PostgreSQL (with `application-local.properties`).
|
||||
|
||||
## References
|
||||
|
||||
- Research: `docs/agents/research/2026-03-04-t4-development-infrastructure.md`
|
||||
- T-4 spec: `spec/setup-tasks.md` (lines 79-98)
|
||||
- Spring Boot Testcontainers: `@ServiceConnection` pattern (Spring Boot 3.1+)
|
||||
- Liquibase Spring Boot integration: auto-configured when `liquibase-core` is on classpath
|
||||
359
docs/agents/research/2026-03-04-t4-development-infrastructure.md
Normal file
359
docs/agents/research/2026-03-04-t4-development-infrastructure.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
date: 2026-03-04T19:37:59.203261+00:00
|
||||
git_commit: cb0bcad145b03fec63be0ee3c1fca46ee545329e
|
||||
branch: master
|
||||
topic: "T-4: Development Infrastructure Setup"
|
||||
tags: [research, codebase, t4, database, liquibase, testcontainers, router, test-infrastructure, docker-compose]
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Research: T-4 Development Infrastructure Setup
|
||||
|
||||
## Research Question
|
||||
|
||||
What is the current state of the codebase relative to T-4's acceptance criteria? What already exists, what is missing, and what are the technical considerations for each criterion?
|
||||
|
||||
## Summary
|
||||
|
||||
T-4 is the final infrastructure task before user story implementation can begin. It bridges the gap between the existing project scaffolds (T-1, T-2, T-3, T-5 — all complete) and actual feature development with TDD. The task covers six areas: database migration framework, database connectivity, environment variable configuration, SPA router, backend test infrastructure, frontend test infrastructure, docker-compose documentation, and container verification with PostgreSQL.
|
||||
|
||||
The codebase already has partial coverage: Vue Router exists with placeholder routes, frontend test infrastructure (Vitest + @vue/test-utils) is operational, and backend test infrastructure (JUnit 5 + Spring Boot Test + MockMvc) is partially in place. What's missing: JPA/Liquibase, Testcontainers, environment variable wiring, and docker-compose documentation.
|
||||
|
||||
## Detailed Findings
|
||||
|
||||
### AC 1: Database Migration Framework (Liquibase)
|
||||
|
||||
**Current state:** Not present. No Liquibase or Liquibase dependency in `pom.xml`. No migration files anywhere in the project. The CLAUDE.md explicitly states "No JPA until T-4."
|
||||
|
||||
**What's needed:**
|
||||
- Add `spring-boot-starter-data-jpa` dependency to `backend/pom.xml`
|
||||
- Add `liquibase-core` dependency (Spring Boot manages the version)
|
||||
- Create changelog directory at `backend/src/main/resources/db/changelog/`
|
||||
- Create master changelog: `db.changelog-master.xml` that includes individual changesets
|
||||
- Create first empty/baseline changeset to prove the tooling works
|
||||
- Spring Boot auto-configures Liquibase when it's on the classpath and a datasource is available — no explicit `@Bean` config needed
|
||||
|
||||
**Spring Boot + Liquibase conventions:**
|
||||
- Default changelog location: `classpath:db/changelog/db.changelog-master.xml`
|
||||
- Format: XML (chosen for schema validation and explicitness)
|
||||
- Changelogs are DB-agnostic — Liquibase generates dialect-specific SQL at runtime
|
||||
- Spring Boot 3.5.x ships Liquibase via its dependency management
|
||||
- Liquibase runs automatically on startup before JPA entity validation
|
||||
|
||||
**Architectural note:** The hexagonal architecture has an existing `adapter.out.persistence` package (currently empty, with `package-info.java`). JPA repositories and entity classes will go here. Domain model classes remain in `domain.model` without JPA annotations — the persistence adapter maps between them. The existing ArchUnit tests already enforce this boundary.
|
||||
|
||||
### AC 2: Database Connectivity via Environment Variables
|
||||
|
||||
**Current state:** `application.properties` has no datasource configuration. Only `spring.application.name=fete` and actuator settings.
|
||||
|
||||
**What's needed:**
|
||||
- Configure Spring datasource properties to read from environment variables via profile-based separation
|
||||
|
||||
**Chosen approach: Profile-based separation with generic env vars.**
|
||||
|
||||
The properties are split across three files with clear responsibilities:
|
||||
|
||||
**`application.properties`** — environment-independent, always active:
|
||||
```properties
|
||||
spring.application.name=fete
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.open-in-view=false
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
```
|
||||
|
||||
**`application-prod.properties`** — committed, production profile, activated in Docker via `ENV SPRING_PROFILES_ACTIVE=prod`:
|
||||
```properties
|
||||
spring.datasource.url=${DATABASE_URL}
|
||||
spring.datasource.username=${DATABASE_USERNAME}
|
||||
spring.datasource.password=${DATABASE_PASSWORD}
|
||||
```
|
||||
|
||||
**`application-local.properties`** — gitignored, developer creates from `.example` template:
|
||||
```properties
|
||||
spring.datasource.url=jdbc:postgresql://localhost:5432/fete
|
||||
spring.datasource.username=fete
|
||||
spring.datasource.password=fete
|
||||
```
|
||||
|
||||
**`application-local.properties.example`** — committed as template, never directly used.
|
||||
|
||||
**Dockerfile:**
|
||||
```dockerfile
|
||||
ENV SPRING_PROFILES_ACTIVE=prod
|
||||
```
|
||||
|
||||
Key points:
|
||||
- No datasource defaults in `application.properties` — if neither profile nor env vars are set, the app fails to start (intentional: no silent fallback to a nonexistent DB)
|
||||
- Generic env var names (`DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`) — the container user never sees Spring property names
|
||||
- `ddl-auto=validate` ensures Hibernate validates entities against the Liquibase-managed schema but never modifies it
|
||||
- `open-in-view=false` prevents the anti-pattern of lazy-loading in views (also avoids Spring Boot's startup warning)
|
||||
- PostgreSQL JDBC driver (`org.postgresql:postgresql`) is needed — Spring Boot manages the version
|
||||
- Tests use `@ServiceConnection` (Testcontainers) which auto-configures the datasource — no profile or env vars needed for tests
|
||||
|
||||
### AC 3: All Runtime Configuration via Environment Variables
|
||||
|
||||
**Current state:** No environment-variable-driven configuration exists beyond Spring Boot defaults.
|
||||
|
||||
**What's needed beyond database:**
|
||||
- Unsplash API key: optional, used by US-16
|
||||
- Max active events: optional, used by US-13
|
||||
|
||||
**Implementation pattern:** `@ConfigurationProperties(prefix = "fete")` class (`FeteProperties`) in `de.fete.config`. Type-safe, validatable, testable.
|
||||
|
||||
These properties also go in `application-prod.properties` with generic env var mapping:
|
||||
```properties
|
||||
fete.unsplash.api-key=${UNSPLASH_API_KEY:}
|
||||
fete.max-active-events=${MAX_ACTIVE_EVENTS:0}
|
||||
```
|
||||
|
||||
Empty `UNSPLASH_API_KEY` = feature disabled. `MAX_ACTIVE_EVENTS=0` = unlimited.
|
||||
|
||||
**Note:** These properties are only scaffolded in T-4 (the `FeteProperties` class with fields and defaults). The business logic using them comes with US-13/US-16.
|
||||
|
||||
### AC 4: SPA Router Configuration
|
||||
|
||||
**Current state:** Vue Router IS configured and operational.
|
||||
|
||||
**File:** `frontend/src/router/index.ts`
|
||||
- Uses `createWebHistory` (HTML5 History API — clean URLs, no hash)
|
||||
- Two routes defined: `/` (HomeView, eager) and `/about` (AboutView, lazy-loaded)
|
||||
- Router is registered in `main.ts` via `app.use(router)`
|
||||
|
||||
**Backend SPA support:** Already implemented in `WebConfig.java`:
|
||||
- `PathResourceResolver` falls back to `index.html` for any path not matching a static file
|
||||
- This enables client-side routing — the backend serves `index.html` for all non-API, non-static paths
|
||||
|
||||
**Assessment:** This AC is effectively already met. The router exists, uses history mode, and the backend supports it. What will change during user stories: routes will be added (e.g., `/event/:token`, `/event/:token/edit`), but the infrastructure is in place.
|
||||
|
||||
### AC 5: Backend Test Infrastructure
|
||||
|
||||
**Current state:** Partially in place.
|
||||
|
||||
**What exists:**
|
||||
- JUnit 5 (via `spring-boot-starter-test`) — operational
|
||||
- Spring Boot Test with `@SpringBootTest` — operational
|
||||
- MockMvc for REST endpoint testing — operational (`FeteApplicationTest.java`, `WebConfigTest.java`)
|
||||
- ArchUnit for architecture validation — operational (`HexagonalArchitectureTest.java`)
|
||||
- Surefire configured with fail-fast (`skipAfterFailureCount=1`)
|
||||
- Test logging configured (`logback-test.xml` at WARN level)
|
||||
|
||||
**What's missing:**
|
||||
- **Testcontainers** — not in `pom.xml`, no test configuration for it
|
||||
- **Integration test support with real PostgreSQL** — currently no database tests exist (because no database exists yet)
|
||||
|
||||
**What's needed:**
|
||||
- Add `org.testcontainers:postgresql` dependency (test scope)
|
||||
- Add `org.testcontainers:junit-jupiter` dependency (test scope) — JUnit 5 integration
|
||||
- Add `spring-boot-testcontainers` dependency (test scope) — Spring Boot 3.1+ Testcontainers integration
|
||||
- Create a test configuration class or use `@ServiceConnection` annotation (Spring Boot 3.1+) for automatic datasource wiring in tests
|
||||
|
||||
**Spring Boot 3.5 + Testcontainers pattern (TestApplication):**
|
||||
|
||||
A `TestFeteApplication.java` in `src/test/` registers Testcontainers beans. All `@SpringBootTest` tests automatically get a PostgreSQL instance — no per-test wiring needed. Existing tests (`FeteApplicationTest`, `WebConfigTest`) continue to work without modification.
|
||||
|
||||
```java
|
||||
// src/test/java/de/fete/TestFeteApplication.java
|
||||
@TestConfiguration(proxyBeanMethods = false)
|
||||
public class TestcontainersConfig {
|
||||
@Bean
|
||||
@ServiceConnection
|
||||
PostgreSQLContainer<?> postgresContainer() {
|
||||
return new PostgreSQLContainer<>("postgres:17-alpine");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With `@ServiceConnection`, Spring Boot auto-configures the datasource to point at the Testcontainers-managed PostgreSQL — no manual URL/username/password wiring needed. Testcontainers starts one shared container per test suite run, not per test class.
|
||||
|
||||
**Important:** Once JPA is on the classpath, every `@SpringBootTest` needs a datasource. The TestApplication pattern ensures this globally. Without it, all existing `@SpringBootTest` tests would break immediately.
|
||||
|
||||
**Test categories after T-4:**
|
||||
- Unit tests: no Spring context, no database — fast, test domain logic
|
||||
- Integration tests: `@SpringBootTest` + Testcontainers — test full stack including database
|
||||
- Architecture tests: ArchUnit — already in place
|
||||
|
||||
### AC 6: Frontend Test Infrastructure
|
||||
|
||||
**Current state:** Already in place and operational.
|
||||
|
||||
**What exists:**
|
||||
- Vitest configured (`vitest.config.ts`): jsdom environment, bail=1, e2e excluded
|
||||
- `@vue/test-utils` v2.4.6 installed — Vue component mounting and assertions
|
||||
- TypeScript test config (`tsconfig.vitest.json`) with jsdom types
|
||||
- Sample test exists: `components/__tests__/HelloWorld.spec.ts` — mounts component, asserts text
|
||||
- Test command: `npm run test:unit` (runs Vitest in watch mode) / `npm run test:unit -- --run` (single run)
|
||||
|
||||
**Assessment:** This AC is already met. The test infrastructure is functional with a passing sample test.
|
||||
|
||||
### AC 7: Both Test Suites Executable
|
||||
|
||||
**Current state:** Both work.
|
||||
|
||||
- Backend: `cd backend && ./mvnw test` — runs JUnit 5 tests (3 tests in 3 classes)
|
||||
- Frontend: `cd frontend && npm run test:unit -- --run` — runs Vitest (1 test in 1 file)
|
||||
- CI pipeline (`ci.yaml`) already runs both in parallel
|
||||
|
||||
**Assessment:** Already met. Will remain met after adding Testcontainers (new tests use the same `./mvnw test` command).
|
||||
|
||||
### AC 8: README Docker-Compose Documentation
|
||||
|
||||
**Current state:** No docker-compose file or documentation exists. The README covers development setup and code quality but has no deployment section.
|
||||
|
||||
**What's needed:**
|
||||
- A `docker-compose.yml` example (either in-repo or documented in README)
|
||||
- Must include: app service (the fete container) + postgres service
|
||||
- Must document required environment variables: `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`
|
||||
- Must document optional environment variables: `UNSPLASH_API_KEY`, `MAX_ACTIVE_EVENTS`
|
||||
- Per CLAUDE.md: "A docker-compose example in the README is sufficient" — no separate file in repo
|
||||
|
||||
**Example structure:**
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
environment:
|
||||
POSTGRES_DB: fete
|
||||
POSTGRES_USER: fete
|
||||
POSTGRES_PASSWORD: changeme
|
||||
volumes:
|
||||
- fete-db:/var/lib/postgresql/data
|
||||
|
||||
app:
|
||||
image: gitea.example.com/user/fete:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DATABASE_URL: jdbc:postgresql://db:5432/fete
|
||||
DATABASE_USERNAME: fete
|
||||
DATABASE_PASSWORD: changeme
|
||||
# MAX_ACTIVE_EVENTS: 100 # optional
|
||||
# UNSPLASH_API_KEY: abc123 # optional
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
fete-db:
|
||||
```
|
||||
|
||||
### AC 9: Container Health Check with PostgreSQL
|
||||
|
||||
**Current state:** The Dockerfile has a HEALTHCHECK directive that queries `/actuator/health`. Currently the app starts without a database and the health check passes.
|
||||
|
||||
**After T-4:** With JPA and Liquibase on the classpath, Spring Boot will:
|
||||
- Fail to start if no database is reachable (datasource auto-configuration fails)
|
||||
- Include database health in `/actuator/health` automatically (via `DataSourceHealthIndicator`)
|
||||
- Run Liquibase migrations on startup — if migrations fail, the app won't start
|
||||
|
||||
**What's needed for verification:**
|
||||
- Start the app with docker-compose (app + postgres)
|
||||
- Verify `/actuator/health` returns `{"status":"UP"}` (which now includes DB connectivity)
|
||||
- Verify Liquibase ran the baseline migration (check `flyway_schema_history` table or app logs)
|
||||
|
||||
## Code References
|
||||
|
||||
### Existing Files (will be modified)
|
||||
|
||||
- `backend/pom.xml:1-170` — Add JPA, Liquibase, PostgreSQL driver, Testcontainers dependencies
|
||||
- `backend/src/main/resources/application.properties:1-4` — Add datasource, JPA, Liquibase, app-specific config
|
||||
- `README.md:1-134` — Add deployment section with docker-compose example
|
||||
|
||||
### Existing Files (relevant context, likely untouched)
|
||||
|
||||
- `backend/src/main/java/de/fete/config/WebConfig.java:1-40` — SPA routing already configured
|
||||
- `backend/src/main/java/de/fete/FeteApplication.java` — Entry point, no changes needed
|
||||
- `frontend/src/router/index.ts:1-23` — Router already configured
|
||||
- `frontend/vitest.config.ts:1-15` — Test infra already configured
|
||||
- `frontend/package.json:1-52` — Test dependencies already present
|
||||
- `.gitea/workflows/ci.yaml` — CI pipeline, may need Testcontainers Docker access for backend tests
|
||||
|
||||
### New Files (to be created)
|
||||
|
||||
- `backend/src/main/resources/db/changelog/db.changelog-master.xml` — Liquibase master changelog
|
||||
- `backend/src/main/resources/application-prod.properties` — Production profile with env var placeholders
|
||||
- `backend/src/main/resources/application-local.properties.example` — Template for local development
|
||||
- `backend/src/test/java/de/fete/TestFeteApplication.java` (or similar) — Testcontainers PostgreSQL bean via TestApplication pattern
|
||||
- `de/fete/config/FeteProperties.java` — `@ConfigurationProperties` class for app-specific settings
|
||||
- README deployment section — docker-compose example inline (no standalone file)
|
||||
- `backend/src/main/resources/application-prod.properties` — Production profile with env var placeholders
|
||||
- `backend/src/main/resources/application-local.properties.example` — Template for local development
|
||||
|
||||
### Package Structure (existing, will gain content)
|
||||
|
||||
- `de.fete.adapter.out.persistence` — JPA entities and Spring Data repositories (empty now)
|
||||
- `de.fete.domain.model` — Domain entities (empty now, no JPA annotations here)
|
||||
- `de.fete.config` — App configuration (WebConfig exists, may add `@ConfigurationProperties` class)
|
||||
|
||||
## Architecture Documentation
|
||||
|
||||
### Hexagonal Architecture and JPA
|
||||
|
||||
The existing ArchUnit tests (`HexagonalArchitectureTest.java`) enforce:
|
||||
- Domain layer must not depend on Spring, adapters, application, or config
|
||||
- Ports (in/out) must be interfaces
|
||||
- Web adapter and persistence adapter must not cross-depend
|
||||
|
||||
This means JPA integration must follow the pattern:
|
||||
1. Domain entities in `domain.model` — plain Java, no JPA annotations
|
||||
2. JPA entities in `adapter.out.persistence` — annotated with `@Entity`, `@Table`, etc.
|
||||
3. Mapping between domain and JPA entities in the persistence adapter
|
||||
4. Repository interfaces (Spring Data) in `adapter.out.persistence`
|
||||
5. Port interfaces in `domain.port.out` — define what the domain needs from persistence
|
||||
6. Service implementations in `application.service` — use port interfaces, not repositories directly
|
||||
|
||||
This is a well-established hexagonal pattern. The ArchUnit tests will automatically validate any new code follows these boundaries.
|
||||
|
||||
### Test Architecture After T-4
|
||||
|
||||
```
|
||||
Test Type | Context | Database | Speed | Purpose
|
||||
-------------------|---------------|-----------------|---------|---------------------------
|
||||
Unit tests | None | None | Fast | Domain logic, services
|
||||
Integration tests | SpringBoot | Testcontainers | Medium | Full stack, DB queries
|
||||
Architecture tests | None (static) | None | Fast | Structural validation
|
||||
```
|
||||
|
||||
All test types run via `./mvnw test`. Testcontainers starts/stops PostgreSQL containers automatically — no external setup needed. The CI pipeline already has Docker available (runs on `ubuntu-latest` with Docker socket).
|
||||
|
||||
### CI Pipeline Considerations
|
||||
|
||||
The current CI pipeline runs `./mvnw -B verify` for backend tests. Testcontainers requires Docker socket access. On Gitea Actions with `ubuntu-latest` runners, Docker is typically available. If the runner uses a Docker-in-Docker setup, the Testcontainers `DOCKER_HOST` environment variable may need configuration — but this is a runtime concern, not a code concern.
|
||||
|
||||
### Spring Boot Profiles
|
||||
|
||||
Currently no profiles are configured. For T-4, a `test` profile may be useful to separate test-specific configuration (e.g., Testcontainers datasource) from production defaults. Spring Boot's `@ActiveProfiles("test")` on test classes or `application-test.properties` can handle this. However, with `@ServiceConnection`, Testcontainers auto-configures the datasource without profile-specific properties.
|
||||
|
||||
## Acceptance Criteria Status Matrix
|
||||
|
||||
| # | Criterion | Current Status | Work Required |
|
||||
|---|-----------|----------------|---------------|
|
||||
| 1 | Liquibase configured with first changelog | Not started | Add `liquibase-core`, create changelog dir and master XML |
|
||||
| 2 | External PostgreSQL via env var | Not started | Add datasource properties with env var placeholders |
|
||||
| 3 | All runtime config via env vars | Not started | Add datasource + app-specific properties |
|
||||
| 4 | SPA router configured | **Done** | Vue Router with history mode already works |
|
||||
| 5 | Backend test infra (Testcontainers) | Partial | JUnit 5 + MockMvc exist; add Testcontainers |
|
||||
| 6 | Frontend test infra | **Done** | Vitest + @vue/test-utils operational |
|
||||
| 7 | Both test suites executable | **Done** | Both `./mvnw test` and `npm run test:unit` work |
|
||||
| 8 | README docker-compose documentation | Not started | Add deployment section with example |
|
||||
| 9 | Container health with PostgreSQL | Not started | Verify after JPA/Liquibase are added |
|
||||
|
||||
## Resolved Decisions
|
||||
|
||||
1. **Liquibase** for database migrations, **XML** format. DB-agnostic changelogs — Liquibase generates dialect-specific SQL at runtime. XML chosen over YAML for schema validation and explicitness. The project intentionally avoids PostgreSQL-specific features in migrations to keep the database layer portable.
|
||||
|
||||
2. **Profile-based properties separation** with generic environment variable names. Three files: `application.properties` (environment-independent, always active), `application-prod.properties` (committed, maps `${DATABASE_URL}` etc. to Spring properties, activated in Docker via `ENV SPRING_PROFILES_ACTIVE=prod`), `application-local.properties` (gitignored, concrete local values, activated via `-Dspring-boot.run.profiles=local`). A committed `.example` template guides developers. The container user sets `DATABASE_URL`, `DATABASE_USERNAME`, `DATABASE_PASSWORD` — never sees Spring internals.
|
||||
|
||||
3. **`@ConfigurationProperties`** for app-specific settings (`FeteProperties` class). Type-safe, validatable, testable. Properties: `fete.unsplash.api-key` (from `UNSPLASH_API_KEY`) and `fete.max-active-events` (from `MAX_ACTIVE_EVENTS`). Both are only scaffolded in T-4; business logic using them comes with US-13/US-16.
|
||||
|
||||
4. **docker-compose example in README only** — no standalone `docker-compose.yml` in the repo. Per CLAUDE.md: "A docker-compose example in the README is sufficient." A local docker-compose for development may be added later separately.
|
||||
|
||||
5. **TestApplication pattern** for Testcontainers integration. A `TestFeteApplication.java` in `src/test/` registers a `@ServiceConnection` PostgreSQL container. All `@SpringBootTest` tests automatically get a database — existing tests continue to work without modification.
|
||||
|
||||
6. **README erweitern** with local development setup documentation (how to copy `application-local.properties.example`, start with profile, PostgreSQL prerequisites).
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Testcontainers in CI:** The Gitea Actions runner needs Docker available for Testcontainers. This works out-of-the-box on `ubuntu-latest` but should be verified after implementation.
|
||||
|
||||
2. **Baseline changelog content:** The first Liquibase changeset should be a minimal, empty changeset that proves the tooling works. No schema needed yet — US-1 will create the first real table.
|
||||
@@ -81,15 +81,15 @@
|
||||
**Description:** Set up the development foundation needed before the first user story can be implemented with TDD (as required by CLAUDE.md). This bridges the gap between project scaffolds and actual feature development. Also includes the database and environment variable configuration deferred from T-2.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- [ ] Database migration framework (Flyway or Liquibase) is configured in the backend with a first empty migration that runs successfully against a PostgreSQL instance
|
||||
- [ ] App connects to external PostgreSQL via environment variable (e.g. `DATABASE_URL` or Spring-native `SPRING_DATASOURCE_*`)
|
||||
- [ ] All runtime configuration via environment variables: database connection, optional Unsplash API key, optional max active events
|
||||
- [ ] SPA router is configured in the Vue frontend (Vue Router) so pages can be navigated by URL path
|
||||
- [ ] Backend test infrastructure is set up: JUnit 5 with Spring Boot Test, plus integration test support using Testcontainers (PostgreSQL) so tests can run against a real database without external setup
|
||||
- [ ] Frontend test infrastructure is set up: Vitest with @vue/test-utils configured and a sample test runs successfully
|
||||
- [ ] Both test suites (backend and frontend) can be executed via their respective build tools (`mvn test` and `npm test` / `npx vitest`)
|
||||
- [ ] README documents deployment setup with a docker-compose example (app + postgres)
|
||||
- [ ] Container starts and responds to health checks with a running PostgreSQL (migrations run on startup)
|
||||
- [x] Database migration framework (Flyway or Liquibase) is configured in the backend with a first empty migration that runs successfully against a PostgreSQL instance
|
||||
- [x] App connects to external PostgreSQL via environment variable (e.g. `DATABASE_URL` or Spring-native `SPRING_DATASOURCE_*`)
|
||||
- [x] All runtime configuration via environment variables: database connection, optional Unsplash API key, optional max active events
|
||||
- [x] SPA router is configured in the Vue frontend (Vue Router) so pages can be navigated by URL path
|
||||
- [x] Backend test infrastructure is set up: JUnit 5 with Spring Boot Test, plus integration test support using Testcontainers (PostgreSQL) so tests can run against a real database without external setup
|
||||
- [x] Frontend test infrastructure is set up: Vitest with @vue/test-utils configured and a sample test runs successfully
|
||||
- [x] Both test suites (backend and frontend) can be executed via their respective build tools (`mvn test` and `npm test` / `npx vitest`)
|
||||
- [x] README documents deployment setup with a docker-compose example (app + postgres)
|
||||
- [x] Container starts and responds to health checks with a running PostgreSQL (migrations run on startup)
|
||||
|
||||
**Dependencies:** T-2, T-5
|
||||
|
||||
|
||||
Reference in New Issue
Block a user