diff --git a/src/main/java/auth_service/configuration/CacheConfig.java b/src/main/java/auth_service/configuration/CacheConfig.java index 6e99f1a..9578be7 100644 --- a/src/main/java/auth_service/configuration/CacheConfig.java +++ b/src/main/java/auth_service/configuration/CacheConfig.java @@ -22,7 +22,7 @@ public class CacheConfig { @Bean public CacheManager cacheManager() { log.info("########### Productive cache active ###########"); - CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail", "publicStats"); cacheManager.setCaffeine(caffeineCacheBuilder()); return cacheManager; } diff --git a/src/main/java/auth_service/configuration/DevCacheConfig.java b/src/main/java/auth_service/configuration/DevCacheConfig.java index 77467fe..994577d 100644 --- a/src/main/java/auth_service/configuration/DevCacheConfig.java +++ b/src/main/java/auth_service/configuration/DevCacheConfig.java @@ -22,7 +22,7 @@ public class DevCacheConfig { @Bean public CacheManager cacheManager() { log.error("########### DEVELOPMENT CACHE ACTIVE ###########"); - CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail", "publicStats"); cacheManager.setCaffeine(caffeineCacheBuilder()); return cacheManager; } diff --git a/src/main/java/auth_service/configuration/security/SecurityConfig.java b/src/main/java/auth_service/configuration/security/SecurityConfig.java index 0161f7f..7829f00 100644 --- a/src/main/java/auth_service/configuration/security/SecurityConfig.java +++ b/src/main/java/auth_service/configuration/security/SecurityConfig.java @@ -35,6 +35,7 @@ public class SecurityConfig { private static final String[] WHITE_LIST_URLS = { "/api/auth/**", // Allow login, registration, etc. "/api/registration/**", // Allow new user registration + "/api/stats/public", // Public landing-page counters (cached, no PII) "/actuator/health", // Allow health checks (e.g., for Docker/Traefik) "/actuator/prometheus", // Allow Prometheus to scrape metrics "/error" // Default Spring Boot error page routing diff --git a/src/main/java/auth_service/modules/stats/api/PublicStatsController.java b/src/main/java/auth_service/modules/stats/api/PublicStatsController.java new file mode 100644 index 0000000..069d1ee --- /dev/null +++ b/src/main/java/auth_service/modules/stats/api/PublicStatsController.java @@ -0,0 +1,22 @@ +package auth_service.modules.stats.api; + +import auth_service.modules.stats.api.dto.PublicStatsDto; +import auth_service.modules.stats.service.PublicStatsService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/stats") +@RequiredArgsConstructor +public class PublicStatsController { + + private final PublicStatsService publicStatsService; + + @GetMapping("/public") + public ResponseEntity getPublicStats() { + return ResponseEntity.ok(publicStatsService.getPublicStats()); + } +} diff --git a/src/main/java/auth_service/modules/stats/api/dto/PublicStatsDto.java b/src/main/java/auth_service/modules/stats/api/dto/PublicStatsDto.java new file mode 100644 index 0000000..54e37c7 --- /dev/null +++ b/src/main/java/auth_service/modules/stats/api/dto/PublicStatsDto.java @@ -0,0 +1,6 @@ +package auth_service.modules.stats.api.dto; + +public record PublicStatsDto( + long totalUsers, + long usersWithSteamLinked +) {} diff --git a/src/main/java/auth_service/modules/stats/repo/StatsRepository.java b/src/main/java/auth_service/modules/stats/repo/StatsRepository.java new file mode 100644 index 0000000..3565e23 --- /dev/null +++ b/src/main/java/auth_service/modules/stats/repo/StatsRepository.java @@ -0,0 +1,27 @@ +package auth_service.modules.stats.repo; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class StatsRepository { + + private final JdbcTemplate jdbc; + + @Autowired + public StatsRepository(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } + + public long countTotalUsers() { + Long v = jdbc.queryForObject("SELECT COUNT(*) FROM users WHERE enabled = TRUE", Long.class); + return v != null ? v : 0L; + } + + public long countUsersWithSteamLinked() { + Long v = jdbc.queryForObject( + "SELECT COUNT(*) FROM users WHERE enabled = TRUE AND steam_id IS NOT NULL", Long.class); + return v != null ? v : 0L; + } +} diff --git a/src/main/java/auth_service/modules/stats/service/PublicStatsService.java b/src/main/java/auth_service/modules/stats/service/PublicStatsService.java new file mode 100644 index 0000000..47bb98b --- /dev/null +++ b/src/main/java/auth_service/modules/stats/service/PublicStatsService.java @@ -0,0 +1,22 @@ +package auth_service.modules.stats.service; + +import auth_service.modules.stats.api.dto.PublicStatsDto; +import auth_service.modules.stats.repo.StatsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PublicStatsService { + + private final StatsRepository statsRepository; + + @Cacheable(value = "publicStats", key = "'global'") + public PublicStatsDto getPublicStats() { + return new PublicStatsDto( + statsRepository.countTotalUsers(), + statsRepository.countUsersWithSteamLinked() + ); + } +} diff --git a/src/main/java/auth_service/modules/user/service/UserService.java b/src/main/java/auth_service/modules/user/service/UserService.java index 7cee95d..a36afe0 100644 --- a/src/main/java/auth_service/modules/user/service/UserService.java +++ b/src/main/java/auth_service/modules/user/service/UserService.java @@ -6,8 +6,8 @@ import auth_service.modules.user.model.UserSettings; import auth_service.modules.user.persistence.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,13 +29,13 @@ public class UserService { return userRepository.findByEmail(email).orElseThrow(() -> new UserNotFoundException("Could not find user with email " + email)); } - @Async @Transactional + @CacheEvict(value = "usersByEmail", allEntries = true) public void updateUserSettings(long userId, long steamId, UserSettings userSettings) { var updated = userRepository.updateSettings(userId, steamId, userSettings); if (updated) { - log.info("Steam id has changed!"); + log.info("Steam id has changed for user {}", userId); } - log.info("User settings has been updated!"); + log.info("User settings updated for user {}", userId); } }