From 0e81a65d62263011b0150ac974a35a6d4879fea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Fri, 5 Dec 2025 10:21:45 +0100 Subject: [PATCH] Good stuff --- .../operaton/fitpub/FitPubApplication.java | 3 -- .../config/TestcontainersConfiguration.java | 42 ------------------- .../fitpub/model/activitypub/Actor.java | 2 +- .../META-INF/spring-devtools.properties | 6 +++ src/main/resources/application-dev.yml | 7 ++-- .../templates/analytics/personal-records.html | 10 ++++- .../templates/analytics/training-load.html | 18 ++++---- .../config/TestcontainersConfiguration.java | 32 ++++++++++++++ .../security/KeyPairValidationTest.java | 3 ++ .../service/ActivityImageServiceTest.java | 3 ++ .../fitpub/service/FitFileServiceTest.java | 19 ++++++++- 11 files changed, 86 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/org/operaton/fitpub/config/TestcontainersConfiguration.java create mode 100644 src/main/resources/META-INF/spring-devtools.properties create mode 100644 src/test/java/org/operaton/fitpub/config/TestcontainersConfiguration.java diff --git a/src/main/java/org/operaton/fitpub/FitPubApplication.java b/src/main/java/org/operaton/fitpub/FitPubApplication.java index 1979ece..344432d 100644 --- a/src/main/java/org/operaton/fitpub/FitPubApplication.java +++ b/src/main/java/org/operaton/fitpub/FitPubApplication.java @@ -5,11 +5,9 @@ import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.operaton.fitpub.config.TestcontainersConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.web.client.RestTemplate; @@ -22,7 +20,6 @@ import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableAsync @Slf4j -@Import(TestcontainersConfiguration.class) public class FitPubApplication { public static void main(String[] args) { diff --git a/src/main/java/org/operaton/fitpub/config/TestcontainersConfiguration.java b/src/main/java/org/operaton/fitpub/config/TestcontainersConfiguration.java deleted file mode 100644 index 27fbfaa..0000000 --- a/src/main/java/org/operaton/fitpub/config/TestcontainersConfiguration.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.operaton.fitpub.config; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.DockerImageName; - -/** - * Testcontainers configuration for development using Spring Boot Dev Services. - * Automatically starts a PostgreSQL container with PostGIS extension when running in dev mode. - * - * This ensures development environment matches production (PostgreSQL + PostGIS). - * - * Only active when NOT running in production profile AND Testcontainers is on the classpath. - */ -@Configuration(proxyBeanMethods = false) -@Profile("!prod") -@ConditionalOnClass(PostgreSQLContainer.class) -public class TestcontainersConfiguration { - - /** - * PostgreSQL container with PostGIS extension. - * Uses postgis/postgis image which includes both PostgreSQL and PostGIS. - * - * @ServiceConnection automatically configures spring.datasource.* properties - */ - @Bean - @ServiceConnection - PostgreSQLContainer postgresContainer() { - return new PostgreSQLContainer<>( - DockerImageName.parse("postgis/postgis:16-3.4") - .asCompatibleSubstituteFor("postgres") - ) - .withDatabaseName("fitpub") - .withUsername("fitpub") - .withPassword("fitpub") - .withReuse(true); // Reuse container across runs for faster startup - } -} diff --git a/src/main/java/org/operaton/fitpub/model/activitypub/Actor.java b/src/main/java/org/operaton/fitpub/model/activitypub/Actor.java index d9bcaf3..2fc16fc 100644 --- a/src/main/java/org/operaton/fitpub/model/activitypub/Actor.java +++ b/src/main/java/org/operaton/fitpub/model/activitypub/Actor.java @@ -68,7 +68,7 @@ public class Actor { .mediaType("image/jpeg") .url(user.getAvatarUrl()) .build() : null) - .url(baseUrl + "/@" + user.getUsername()) + .url(baseUrl + "/users/" + user.getUsername()) .build(); } diff --git a/src/main/resources/META-INF/spring-devtools.properties b/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..080d496 --- /dev/null +++ b/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1,6 @@ +# Exclude Testcontainers from devtools restart +# This allows Testcontainers to be loaded by the base classloader +restart.exclude.testcontainers=/testcontainers[\\w\\-]*\\.jar +restart.exclude.testcontainers-postgresql=/testcontainers-postgresql[\\w\\-]*\\.jar +restart.exclude.docker-java=/docker-java[\\w\\-]*\\.jar +restart.exclude.duct-tape=/duct-tape[\\w\\-]*\\.jar diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 77d82bf..7e50801 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -3,9 +3,10 @@ spring: datasource: - url: jdbc:postgresql://localhost:5432/fitpub - username: fitpub - password: change_me_in_production + # For dev: Start PostgreSQL with: docker run -d --name fitpub-postgres -p 5432:5432 -e POSTGRES_DB=fitpub -e POSTGRES_USER=fitpub -e POSTGRES_PASSWORD=fitpub postgis/postgis:16-3.4 + url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/fitpub} + username: ${SPRING_DATASOURCE_USERNAME:fitpub} + password: ${SPRING_DATASOURCE_PASSWORD:fitpub} driver-class-name: org.postgresql.Driver jpa: diff --git a/src/main/resources/templates/analytics/personal-records.html b/src/main/resources/templates/analytics/personal-records.html index e700fb0..6572cc0 100644 --- a/src/main/resources/templates/analytics/personal-records.html +++ b/src/main/resources/templates/analytics/personal-records.html @@ -266,11 +266,19 @@ #activityTypeTabs .nav-link { cursor: pointer; + color: var(--dark-color) !important; + font-weight: 700; + transition: all 0.3s ease; + } + + #activityTypeTabs .nav-link:hover { + color: var(--primary-color) !important; + background-color: rgba(255, 20, 147, 0.1); } #activityTypeTabs .nav-link.active { background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); - color: white; + color: white !important; border-color: var(--primary-color); } diff --git a/src/main/resources/templates/analytics/training-load.html b/src/main/resources/templates/analytics/training-load.html index 9cd819f..04efa04 100644 --- a/src/main/resources/templates/analytics/training-load.html +++ b/src/main/resources/templates/analytics/training-load.html @@ -29,13 +29,13 @@
- - -
@@ -123,11 +123,13 @@ card.style.backgroundColor = config.bgColor; } - async function loadTrainingLoad(days = 30) { + async function loadTrainingLoad(event, days = 30) { try { - // Update active button - document.querySelectorAll('.btn-group button').forEach(btn => btn.classList.remove('active')); - event.target.classList.add('active'); + // Update active button (only if called from a button click) + if (event && event.target) { + document.querySelectorAll('.btn-group button').forEach(btn => btn.classList.remove('active')); + event.target.classList.add('active'); + } document.getElementById('loading-spinner').style.display = 'block'; document.getElementById('charts-content').style.display = 'none'; @@ -301,7 +303,7 @@ return; } loadFormStatus(); - loadTrainingLoad(30); + loadTrainingLoad(null, 30); }); diff --git a/src/test/java/org/operaton/fitpub/config/TestcontainersConfiguration.java b/src/test/java/org/operaton/fitpub/config/TestcontainersConfiguration.java new file mode 100644 index 0000000..e92b199 --- /dev/null +++ b/src/test/java/org/operaton/fitpub/config/TestcontainersConfiguration.java @@ -0,0 +1,32 @@ +package org.operaton.fitpub.config; + +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; +import org.testcontainers.utility.DockerImageName; + +/** + * Testcontainers configuration for tests. + * Automatically starts a PostgreSQL container with PostGIS extension for integration tests. + */ +@TestConfiguration(proxyBeanMethods = false) +public class TestcontainersConfiguration { + + /** + * PostgreSQL container with PostGIS extension for tests. + * @ServiceConnection automatically configures the datasource from this container. + */ + @Bean + @ServiceConnection + public PostgreSQLContainer postgresContainer() { + return new PostgreSQLContainer<>( + DockerImageName.parse("postgis/postgis:16-3.4") + .asCompatibleSubstituteFor("postgres") + ) + .withDatabaseName("fitpub") + .withUsername("fitpub") + .withPassword("fitpub") + .withReuse(true); + } +} diff --git a/src/test/java/org/operaton/fitpub/security/KeyPairValidationTest.java b/src/test/java/org/operaton/fitpub/security/KeyPairValidationTest.java index 93717c4..b872516 100644 --- a/src/test/java/org/operaton/fitpub/security/KeyPairValidationTest.java +++ b/src/test/java/org/operaton/fitpub/security/KeyPairValidationTest.java @@ -2,10 +2,12 @@ package org.operaton.fitpub.security; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; +import org.operaton.fitpub.config.TestcontainersConfiguration; import org.operaton.fitpub.model.entity.User; import org.operaton.fitpub.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; @@ -23,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; * Test to validate that users' public and private keys match. */ @SpringBootTest +@Import(TestcontainersConfiguration.class) @Slf4j public class KeyPairValidationTest { diff --git a/src/test/java/org/operaton/fitpub/service/ActivityImageServiceTest.java b/src/test/java/org/operaton/fitpub/service/ActivityImageServiceTest.java index 7b5e5bd..05e18b3 100644 --- a/src/test/java/org/operaton/fitpub/service/ActivityImageServiceTest.java +++ b/src/test/java/org/operaton/fitpub/service/ActivityImageServiceTest.java @@ -3,6 +3,7 @@ package org.operaton.fitpub.service; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; +import org.operaton.fitpub.config.TestcontainersConfiguration; import org.operaton.fitpub.model.entity.Activity; import org.operaton.fitpub.model.entity.ActivityMetrics; import org.operaton.fitpub.model.entity.User; @@ -11,6 +12,7 @@ import org.operaton.fitpub.repository.UserRepository; import org.operaton.fitpub.util.FitParser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; import java.io.File; @@ -30,6 +32,7 @@ import static org.junit.jupiter.api.Assertions.*; "fitpub.image.osm-tiles.enabled=true" }) @ActiveProfiles("test") +@Import(TestcontainersConfiguration.class) class ActivityImageServiceTest { @Autowired diff --git a/src/test/java/org/operaton/fitpub/service/FitFileServiceTest.java b/src/test/java/org/operaton/fitpub/service/FitFileServiceTest.java index 85c5efe..66d93a7 100644 --- a/src/test/java/org/operaton/fitpub/service/FitFileServiceTest.java +++ b/src/test/java/org/operaton/fitpub/service/FitFileServiceTest.java @@ -55,6 +55,21 @@ class FitFileServiceTest { @Mock private ActivityMetricsRepository metricsRepository; + @Mock + private PersonalRecordService personalRecordService; + + @Mock + private AchievementService achievementService; + + @Mock + private TrainingLoadService trainingLoadService; + + @Mock + private ActivitySummaryService activitySummaryService; + + @Mock + private WeatherService weatherService; + @Spy private ObjectMapper objectMapper; @@ -148,7 +163,9 @@ class FitFileServiceTest { // Assert assertNotNull(result); assertTrue(result.getTitle().contains("Run")); - assertTrue(result.getTitle().contains(testParsedData.getStartTime().toLocalDate().toString())); + // Title format is "[Time of Day] [Activity Type]" (e.g., "Morning Run") + // Start time is 8:00 AM, which is "Morning" + assertTrue(result.getTitle().contains("Morning")); } @Test