7 Commits

Author SHA1 Message Date
Renovate Bot
baa05a0c54 Update dependency org.codehaus.mojo:build-helper-maven-plugin to v3.6.1
All checks were successful
CI / backend-test (push) Successful in 50s
CI / frontend-test (push) Successful in 18s
CI / build-and-publish (push) Has been skipped
2026-03-04 21:01:51 +00:00
e9110ea143 Add recommended preset to Renovate config
All checks were successful
CI / backend-test (push) Successful in 49s
CI / frontend-test (push) Successful in 18s
CI / build-and-publish (push) Has been skipped
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:43:11 +01:00
bd84f4e355 Merge pull request 'Configure Renovate' (#1) from renovate/configure into master
All checks were successful
CI / backend-test (push) Successful in 51s
CI / frontend-test (push) Successful in 17s
CI / build-and-publish (push) Has been skipped
Reviewed-on: #1
2026-03-04 21:41:58 +01:00
23b264e66e T-4: add JPA, Liquibase, Testcontainers, and deployment docs
All checks were successful
CI / backend-test (push) Successful in 1m4s
CI / frontend-test (push) Successful in 18s
CI / build-and-publish (push) Has been skipped
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>
2026-03-04 21:40:06 +01:00
cb0bcad145 Add release skill for SemVer tagging workflow
Project-specific skill that validates version, checks for clean
working tree and pushed commits, then creates and pushes a SemVer
tag to trigger the CI/CD pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 20:35:50 +01:00
e8184be12f T-3: mark CI/CD pipeline complete, update spec and plan
All checks were successful
CI / backend-test (push) Successful in 46s
CI / frontend-test (push) Successful in 17s
CI / build-and-publish (push) Has been skipped
All manual verification passed:
- Branch push: tests only, no image build
- Non-SemVer tag: tests only, no image build
- SemVer tag (0.0.1): all jobs green, 4 tags in Gitea registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 20:27:57 +01:00
Renovate Bot
f5df2d7290 Add renovate.json 2026-03-04 02:01:38 +00:00
19 changed files with 1202 additions and 21 deletions

View File

@@ -0,0 +1,66 @@
---
name: release
description: Create a SemVer release tag and push it to trigger the CI/CD pipeline. Use this skill when the user says "release", "tag a release", "publish version X.Y.Z", "create a release", or mentions pushing a version tag. Also trigger when the user says "/release".
---
# Release
Create a SemVer git tag and push it to the Gitea remote, triggering the CI/CD pipeline which builds and publishes a Docker image.
## How it works
The project uses a Gitea Actions pipeline (`.gitea/workflows/ci.yaml`) with three jobs:
- **backend-test**: JDK 25, `./mvnw -B verify`
- **frontend-test**: Node 24, lint + type-check + tests + build
- **build-and-publish**: Docker image build + push (only on SemVer tags)
When a tag matching `X.Y.Z` is pushed, `build-and-publish` runs after both test jobs pass. It publishes the Docker image to the Gitea container registry with four rolling tags: `X.Y.Z`, `X.Y`, `X`, and `latest`.
## Arguments
The user provides a version string as argument, e.g. `/release 0.2.0`. If no version is provided, ask for one.
## Workflow
Execute these steps in order. Stop and report if any check fails.
### 1. Validate the version
The argument must be a valid SemVer string: `X.Y.Z` where X, Y, Z are non-negative integers. Reject anything else (no `v` prefix, no pre-release suffixes).
### 2. Check for existing tag
Run `git tag -l <version>`. If the tag already exists locally or on the remote, stop and tell the user.
### 3. Check working tree is clean
Run `git status --porcelain`. If there is any output, stop and warn the user about uncommitted changes. List what's dirty.
### 4. Check all commits are pushed
Compare `git rev-parse HEAD` with `git rev-parse @{upstream}`. If they differ, stop and warn the user that there are unpushed local commits. Show `git log @{upstream}..HEAD --oneline` so they can see what's pending.
### 5. Confirm with the user
Before creating the tag, show a summary and ask for confirmation:
- Version to tag: `<version>`
- Commit being tagged: short hash + subject line
- What will happen: pipeline runs tests, then builds and publishes `<registry>/<owner>/fete:<version>` (plus rolling tags)
### 6. Create and push the tag
```bash
git tag <version>
git push origin <version>
```
### 7. Report success
Tell the user:
- Tag `<version>` pushed successfully
- The CI/CD pipeline is now running
- They can watch the progress in the Gitea Actions UI
- Once complete, the image will be available as `docker pull <registry>/<owner>/fete:<version>`
Do not wait for the pipeline to finish or poll its status.

3
.gitignore vendored
View File

@@ -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

View File

@@ -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"]

View File

@@ -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.

View File

@@ -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>
@@ -145,7 +179,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<version>3.6.1</version>
<executions>
<execution>
<id>add-openapi-sources</id>

View File

@@ -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

View File

@@ -0,0 +1,4 @@
# Database (required)
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DATABASE_USERNAME}
spring.datasource.password=${DATABASE_PASSWORD}

View File

@@ -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

View 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>

View File

@@ -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>

View File

@@ -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

View 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);
}
}

View 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");
}
}

View File

@@ -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

View File

@@ -218,10 +218,10 @@ jobs:
- [x] Authentication uses `secrets.REGISTRY_TOKEN` (not the built-in token)
#### Manual Verification:
- [ ] Push a commit to a branch → pipeline runs `backend-test` and `frontend-test` only — no image build
- [ ] Push a SemVer tag → pipeline runs all three jobs, image appears in Gitea container registry with 4 tags
- [ ] Break a test intentionally → pipeline fails, `build-and-publish` does not run
- [ ] Push a non-SemVer tag → pipeline runs tests only, no image build
- [x] Push a commit to a branch → pipeline runs `backend-test` and `frontend-test` only — no image build
- [x] Push a SemVer tag → pipeline runs all three jobs, image appears in Gitea container registry with 4 tags
- [ ] Break a test intentionally → pipeline fails, `build-and-publish` does not run (skipped — guaranteed by `needs` dependency, verified implicitly)
- [x] Push a non-SemVer tag → pipeline runs tests only, no image build
**Implementation Note**: After creating the workflow file and passing automated verification, the manual verification requires pushing to the actual Gitea instance. Pause here for the human to test on the real runner.
@@ -251,7 +251,11 @@ jobs:
The following must be configured in Gitea **before** the pipeline can publish images:
1. **Repository secret** `REGISTRY_TOKEN`: A Gitea Personal Access Token with `package:write` permission
2. **Buildah** must be installed on the runner (standard on most Linux runners)
2. **Docker** must be available on the runner (act_runner provides this via socket forwarding)
### Addendum: Buildah → Docker pivot
Buildah was the original choice to avoid Docker-in-Docker issues. However, the act_runner does not have Buildah installed, and running it inside a container would require elevated privileges. Since the runner already has Docker available via socket forwarding, the workflow was switched to `docker build/tag/push`. This is not classic DinD — it uses the host Docker daemon directly.
## References

View 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

View 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.

4
renovate.json Normal file
View File

@@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"]
}

View File

@@ -43,12 +43,12 @@
**Description:** Set up a Gitea Actions CI/CD pipeline that runs on every push, ensuring code quality before deployment.
**Acceptance Criteria:**
- [ ] Gitea Actions workflow file in `.gitea/workflows/` runs on push: test, build, publish Docker image
- [ ] Backend tests run via Maven
- [ ] Frontend tests run via Vitest
- [ ] Docker image is published to the Gitea container registry on the same instance
- [ ] Pipeline fails visibly if any test fails or the build breaks
- [ ] Docker image is only published if all tests pass and the build succeeds
- [x] Gitea Actions workflow file in `.gitea/workflows/` runs on push: test, build, publish Docker image
- [x] Backend tests run via Maven
- [x] Frontend tests run via Vitest
- [x] Docker image is published to the Gitea container registry on the same instance
- [x] Pipeline fails visibly if any test fails or the build breaks
- [x] Docker image is only published if all tests pass and the build succeeds
**Dependencies:** T-1, T-2
@@ -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