- Move cross-cutting docs (personas, design system, implementation phases, Ideen.md) to .specify/memory/ - Move cross-cutting research and plans to .specify/memory/research/ and .specify/memory/plans/ - Extract 5 setup tasks from spec/setup-tasks.md into individual specs/001-005/spec.md files with spec-kit template format - Extract 20 user stories from spec/userstories.md into individual specs/006-026/spec.md files with spec-kit template format - Relocate feature-specific research and plan docs into specs/[feature]/ - Add spec-kit constitution, templates, scripts, and slash commands - Slim down CLAUDE.md to Claude-Code-specific config, delegate principles to .specify/memory/constitution.md - Update ralph.sh with stream-json output and per-iteration logging - Delete old spec/ and docs/agents/ directories - Gitignore Ralph iteration JSONL logs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
22 KiB
date, git_commit, branch, topic, tags, status
| date | git_commit | branch | topic | tags | status | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-03-04T20:09:31.044992+00:00 | cb0bcad145 |
master | T-4: Development Infrastructure Setup |
|
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 inWebConfig.java - Frontend test infrastructure: Vitest +
@vue/test-utils, sample test passing - Both test suites executable:
./mvnw test(3 tests) andnpm 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 dependenciesbackend/src/main/resources/application.properties:1-4— Only app name + actuatorHexagonalArchitectureTest.java:22—configis already an adapter in ArchUnit rulesFeteApplicationTest.java— Uses@SpringBootTest+ MockMvc; will need datasource after JPA is addedDockerfile:26— NoSPRING_PROFILES_ACTIVEset.gitignore:47-51—.env*patterns exist but noapplication-local.properties
Desired End State
After this plan is complete:
./mvnw testruns all backend tests (including new Testcontainers-backed integration tests) against a real PostgreSQL without external setup./mvnw spring-boot:run -Dspring-boot.run.profiles=localstarts the app against a local PostgreSQL- Docker container starts with
DATABASE_URL/DATABASE_USERNAME/DATABASE_PASSWORDenv vars, runs Liquibase migrations, and responds to health checks - README contains a copy-paste-ready docker-compose example for deployment
FetePropertiesscaffoldsfete.unsplash.api-keyandfete.max-active-events(no business logic yet)
Verification:
cd backend && ./mvnw verify— all tests green, checkstyle + spotbugs passcd frontend && npm run test:unit -- --run— unchanged, still greendocker build .— succeeds- docker-compose (app + postgres) — container starts,
/actuator/healthreturns{"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.ymlfile in repo — inline in README per CLAUDE.md - No
application-local.propertiescommitted — only the.exampletemplate - 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.
<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):
<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 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 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).
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 compilesucceeds (dependencies resolve, checkstyle passes)- Changelog XML files are well-formed (Maven compile does not fail on resource processing)
Manual Verification:
- Verify
pom.xmlhas all six new dependencies with correct scopes - Verify changelog directory structure:
db/changelog/db.changelog-master.xmlincludes000-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.
# 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).
# 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)
FeteProperties configuration properties classFile: 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.
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)
FetePropertiesConfig configuration classFile: backend/src/main/java/de/fete/config/FetePropertiesConfig.java (new)
Changes: Separate @Configuration that enables FeteProperties.
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.
# 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 compilesucceeds (FeteProperties compiles, checkstyle passes)docker build .succeeds
Manual Verification:
application-prod.propertiescontains all five env-var placeholdersapplication-local.properties.exampleis committed;application-local.propertiesis gitignoredFetePropertiesfields:unsplash.apiKey(String),maxActiveEvents(int)- Dockerfile has
ENV SPRING_PROFILES_ACTIVE=prodbeforeENTRYPOINT
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.
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).
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.
@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-runstarts 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.
## 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
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