feat(sprint-5): Phase 1 — Docker Compose full stack, CORS, Next.js upgrade
- Dockerfile.backend: multi-stage Java 21 build (eclipse-temurin) - docker-compose.yml: PostgreSQL 16 + backend + frontend with health checks - SecurityConfig: CORS for localhost:3000 frontend origin - application-docker.properties: Docker profile with env vars - Spring Boot Actuator health endpoint enabled - Next.js upgraded 15.2.8 → 15.5.18 (security fixes)
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Multi-stage build for cannamanage-api (Spring Boot + Java 21)
|
||||
# Build context: repo root (needs access to all Maven modules)
|
||||
|
||||
FROM eclipse-temurin:21-jdk-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy Maven wrapper + POM files first (layer caching)
|
||||
COPY .mvn/ .mvn/
|
||||
COPY mvnw pom.xml ./
|
||||
COPY cannamanage-domain/pom.xml cannamanage-domain/pom.xml
|
||||
COPY cannamanage-service/pom.xml cannamanage-service/pom.xml
|
||||
COPY cannamanage-api/pom.xml cannamanage-api/pom.xml
|
||||
|
||||
# Download dependencies (cached unless POMs change)
|
||||
RUN chmod +x mvnw && ./mvnw dependency:go-offline -B -q 2>/dev/null || true
|
||||
|
||||
# Copy source code
|
||||
COPY cannamanage-domain/src/ cannamanage-domain/src/
|
||||
COPY cannamanage-service/src/ cannamanage-service/src/
|
||||
COPY cannamanage-api/src/ cannamanage-api/src/
|
||||
|
||||
# Build the fat JAR
|
||||
RUN ./mvnw package -pl cannamanage-api -am -DskipTests -B -q
|
||||
|
||||
# --- Runtime stage ---
|
||||
FROM eclipse-temurin:21-jre-alpine AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
|
||||
|
||||
COPY --from=builder /app/cannamanage-api/target/*.jar app.jar
|
||||
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||
@@ -118,6 +118,11 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<!-- Actuator (health endpoint for Docker) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.cannamanage.api.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@@ -13,8 +14,12 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Security configuration — Sprint 3: API + Staff portal with JWT + Member portal with sessions.
|
||||
@@ -38,6 +43,7 @@ public class SecurityConfig {
|
||||
public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.securityMatcher("/api/**")
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
@@ -116,4 +122,21 @@ public class SecurityConfig {
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOrigins(List.of(
|
||||
"http://localhost:3000",
|
||||
"http://frontend:3000"
|
||||
));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*"));
|
||||
config.setAllowCredentials(true);
|
||||
config.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/api/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Docker profile — used when running in Docker Compose
|
||||
spring.datasource.url=${SPRING_DATASOURCE_URL}
|
||||
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
|
||||
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
|
||||
|
||||
# Enable Flyway for container startup (fresh DB)
|
||||
spring.flyway.enabled=true
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
|
||||
# JWT secret from environment
|
||||
cannamanage.security.jwt.secret=${CANNAMANAGE_SECURITY_JWT_SECRET}
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
|
||||
# Disable mail in Docker (no SMTP container)
|
||||
spring.mail.host=localhost
|
||||
spring.mail.port=1025
|
||||
@@ -31,6 +31,10 @@ spring.mail.from=${MAIL_FROM:noreply@cannamanage.de}
|
||||
# App base URL (for invite links)
|
||||
app.base-url=${APP_BASE_URL:http://localhost:8080}
|
||||
|
||||
# Actuator
|
||||
management.endpoints.web.exposure.include=health
|
||||
management.endpoint.health.show-details=never
|
||||
|
||||
# Session configuration (member portal)
|
||||
server.servlet.session.timeout=30m
|
||||
server.servlet.session.cookie.same-site=strict
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
"emoji-picker-react": "4.12.2",
|
||||
"input-otp": "1.4.2",
|
||||
"lucide-react": "0.446.0",
|
||||
"next": "15.2.8",
|
||||
"next": "15.5.18",
|
||||
"next-auth": "5.0.0-beta.31",
|
||||
"next-intl": "^4.13.0",
|
||||
"react": "19.1.3",
|
||||
@@ -72,7 +72,7 @@
|
||||
"@types/react": "19.0.12",
|
||||
"@types/react-dom": "19.0.4",
|
||||
"eslint": "9.18.0",
|
||||
"eslint-config-next": "15.2.8",
|
||||
"eslint-config-next": "15.5.18",
|
||||
"eslint-config-prettier": "10.1.1",
|
||||
"eslint-plugin-prettier": "5.2.3",
|
||||
"playwright": "^1.60.0",
|
||||
|
||||
Generated
+2790
-4594
File diff suppressed because it is too large
Load Diff
+22
-18
@@ -1,17 +1,15 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: cannamanage-db-local
|
||||
container_name: cannamanage-db
|
||||
environment:
|
||||
POSTGRES_DB: cannamanage
|
||||
POSTGRES_USER: cannamanage
|
||||
POSTGRES_PASSWORD: dev_password_change_in_prod
|
||||
POSTGRES_PASSWORD: cannamanage_dev
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pgdata_local:/var/lib/postgresql/data
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U cannamanage"]
|
||||
interval: 5s
|
||||
@@ -21,19 +19,25 @@ services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: cannamanage-api/Dockerfile
|
||||
dockerfile: Dockerfile.backend
|
||||
container_name: cannamanage-backend
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/cannamanage
|
||||
- SPRING_DATASOURCE_USERNAME=cannamanage
|
||||
- SPRING_DATASOURCE_PASSWORD=dev_password_change_in_prod
|
||||
SPRING_PROFILES_ACTIVE: docker
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/cannamanage
|
||||
SPRING_DATASOURCE_USERNAME: cannamanage
|
||||
SPRING_DATASOURCE_PASSWORD: cannamanage_dev
|
||||
CANNAMANAGE_SECURITY_JWT_SECRET: docker-dev-secret-key-minimum-32-characters-long-for-hmac
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
profiles:
|
||||
- full
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/actuator/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
frontend:
|
||||
build:
|
||||
@@ -43,13 +47,13 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- BASE_URL=http://localhost:3000
|
||||
- HOME_PATHNAME=/dashboards/analytics
|
||||
- BACKEND_URL=http://backend:8080
|
||||
NEXTAUTH_URL: http://localhost:3000
|
||||
NEXTAUTH_SECRET: docker-dev-nextauth-secret-minimum-32chars
|
||||
BACKEND_URL: http://backend:8080
|
||||
AUTH_URL: http://localhost:3000
|
||||
depends_on:
|
||||
- backend
|
||||
profiles:
|
||||
- full
|
||||
backend:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
pgdata_local:
|
||||
pgdata:
|
||||
|
||||
Reference in New Issue
Block a user