Good stuff

This commit is contained in:
Tim Zöller 2025-12-05 10:21:45 +01:00
parent 9e428a0499
commit 0e81a65d62
11 changed files with 86 additions and 59 deletions

View file

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

View file

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

View file

@ -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();
}

View file

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

View file

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

View file

@ -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);
}
</style>

View file

@ -29,13 +29,13 @@
<!-- Period Selector -->
<div class="btn-group mb-4 w-100" role="group">
<button type="button" class="btn btn-outline-primary active" onclick="loadTrainingLoad(30)">
<button type="button" class="btn btn-outline-primary active" onclick="loadTrainingLoad(event, 30)">
Last 30 Days
</button>
<button type="button" class="btn btn-outline-primary" onclick="loadTrainingLoad(60)">
<button type="button" class="btn btn-outline-primary" onclick="loadTrainingLoad(event, 60)">
Last 60 Days
</button>
<button type="button" class="btn btn-outline-primary" onclick="loadTrainingLoad(90)">
<button type="button" class="btn btn-outline-primary" onclick="loadTrainingLoad(event, 90)">
Last 90 Days
</button>
</div>
@ -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);
});
</script>
</div>