-
Notifications
You must be signed in to change notification settings - Fork 31
Implement /health and /version endpoints for Admin API #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e71943a
50bbaa0
a373482
bded299
2a5cf68
860e4ce
e1f2346
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package com.iemr.admin.config; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| import com.iemr.admin.utils.JwtAuthenticationUtil; | ||
| import com.iemr.admin.utils.JwtUserIdValidationFilter; | ||
|
|
||
| @Configuration | ||
| public class SecurityFilterConfig { | ||
|
|
||
| @Autowired | ||
| private JwtAuthenticationUtil jwtAuthenticationUtil; | ||
|
|
||
| @Value("${cors.allowed-origins}") | ||
| private String allowedOrigins; | ||
|
|
||
| @Bean | ||
| public FilterRegistrationBean<JwtUserIdValidationFilter> jwtFilterRegistration() { | ||
| FilterRegistrationBean<JwtUserIdValidationFilter> registration = new FilterRegistrationBean<>(); | ||
| registration.setFilter(new JwtUserIdValidationFilter(jwtAuthenticationUtil, allowedOrigins)); | ||
| registration.addUrlPatterns("/*"); | ||
| registration.setOrder(1); | ||
|
|
||
| // Set name for easier debugging | ||
| registration.setName("JwtUserIdValidationFilter"); | ||
|
|
||
| return registration; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * AMRIT β Accessible Medical Records via Integrated Technology | ||
| * Integrated EHR (Electronic Health Records) Solution | ||
| * | ||
| * Copyright (C) "Piramal Swasthya Management and Research Institute" | ||
| * | ||
| * This file is part of AMRIT. | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with this program. If not, see https://www.gnu.org/licenses/. | ||
| */ | ||
| package com.iemr.admin.controller.health; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import com.iemr.admin.service.health.HealthService; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
|
|
||
| @RestController | ||
| public class HealthController { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(HealthController.class); | ||
|
|
||
| @Autowired | ||
| private HealthService healthService; | ||
|
|
||
| @Operation(summary = "Health check endpoint") | ||
| @GetMapping("/health") | ||
| public ResponseEntity<Map<String, Object>> health() { | ||
| logger.info("Health check endpoint called"); | ||
|
|
||
| Map<String, Object> healthStatus = healthService.checkHealth(); | ||
|
|
||
| // Return 503 if any service is down, 200 if all are up | ||
| String status = (String) healthStatus.get("status"); | ||
| HttpStatus httpStatus = "UP".equals(status) ? HttpStatus.OK : HttpStatus.SERVICE_UNAVAILABLE; | ||
|
|
||
| logger.info("Health check completed with status: {}", status); | ||
| return ResponseEntity.status(httpStatus).body(healthStatus); | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can move the logic (like reading Git properties and building the JSON) into a separate service method instead of writing everything inside the controller. Also, letβs review all the suggestions given by CodeRabbit once again. One of the points to check is the use of StringBuilder, as it might cause issues in some cases. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| /* | ||
| * AMRIT β Accessible Medical Records via Integrated Technology | ||
| * Integrated EHR (Electronic Health Records) Solution | ||
| * | ||
| * Copyright (C) "Piramal Swasthya Management and Research Institute" | ||
| * | ||
| * This file is part of AMRIT. | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with this program. If not, see https://www.gnu.org/licenses/. | ||
| */ | ||
| package com.iemr.admin.service.health; | ||
|
|
||
| import java.sql.Connection; | ||
| import java.sql.PreparedStatement; | ||
| import java.sql.ResultSet; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| import javax.sql.DataSource; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.data.redis.core.RedisCallback; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class HealthService { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(HealthService.class); | ||
| private static final String DB_HEALTH_CHECK_QUERY = "SELECT 1 as health_check"; | ||
|
|
||
| @Autowired | ||
| private DataSource dataSource; | ||
|
|
||
| @Autowired(required = false) | ||
| private RedisTemplate<String, Object> redisTemplate; | ||
|
Comment on lines
+48
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
# Description: Find all RedisTemplate bean definitions and analyze duplicate beans
echo "=== Searching for RedisTemplate @Bean definitions ==="
rg -n -B 2 -A 5 '@Bean.*RedisTemplate|@Bean.*redisTemplate' --type java
echo -e "\n=== Searching for any RedisTemplate bean creation ==="
rg -n -B 2 -A 5 'RedisTemplate.*new.*|return.*RedisTemplate' --type java
echo -e "\n=== Checking RoleMasterApplication class ==="
fd 'RoleMasterApplication' --type f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && cat -n {}'
echo -e "\n=== Checking RedisConfig class ==="
fd 'RedisConfig' --type f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && cat -n {}'
echo -e "\n=== Checking application properties for Redis pool config ==="
fd 'application.*properties' --type f | xargs -I {} sh -c 'echo "=== {} ===" && rg "redis|pool" {} -i'
echo -e "\n=== Checking HealthService imports and annotation ==="
head -60 src/main/java/com/iemr/admin/service/health/HealthService.java | cat -nRepository: PSMRI/Admin-API Length of output: 9443 Resolve duplicate RedisTemplate bean definitions. Two @bean methods create Immediate fix: Add @Autowired(required = false)
+@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;Long-term: Rename or remove one conflicting bean definition. RoleMasterApplication creates Also add missing Redis connection pool configuration to spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0π€ Prompt for AI Agents |
||
|
|
||
| public Map<String, Object> checkHealth() { | ||
| Map<String, Object> healthStatus = new HashMap<>(); | ||
| boolean overallHealth = true; | ||
|
|
||
| // Check database connectivity (details logged internally, not exposed) | ||
| boolean dbHealthy = checkDatabaseHealthInternal(); | ||
| if (!dbHealthy) { | ||
| overallHealth = false; | ||
| } | ||
|
|
||
| // Check Redis connectivity if configured (details logged internally) | ||
| if (redisTemplate != null) { | ||
| boolean redisHealthy = checkRedisHealthInternal(); | ||
| if (!redisHealthy) { | ||
| overallHealth = false; | ||
| } | ||
| } | ||
|
|
||
| healthStatus.put("status", overallHealth ? "UP" : "DOWN"); | ||
|
|
||
| logger.info("Health check completed - Overall status: {}", overallHealth ? "UP" : "DOWN"); | ||
| return healthStatus; | ||
| } | ||
|
|
||
| private boolean checkDatabaseHealthInternal() { | ||
| long startTime = System.currentTimeMillis(); | ||
|
|
||
| try (Connection connection = dataSource.getConnection()) { | ||
| boolean isConnectionValid = connection.isValid(2); // 2 second timeout per best practices | ||
|
|
||
| if (isConnectionValid) { | ||
| try (PreparedStatement stmt = connection.prepareStatement(DB_HEALTH_CHECK_QUERY)) { | ||
| stmt.setQueryTimeout(3); // 3 second query timeout | ||
| try (ResultSet rs = stmt.executeQuery()) { | ||
| if (rs.next() && rs.getInt(1) == 1) { | ||
| long responseTime = System.currentTimeMillis() - startTime; | ||
| logger.debug("Database health check: UP ({}ms)", responseTime); | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| logger.warn("Database health check: Connection not valid"); | ||
| return false; | ||
| } catch (Exception e) { | ||
| logger.error("Database health check failed: {}", e.getMessage()); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| private boolean checkRedisHealthInternal() { | ||
| long startTime = System.currentTimeMillis(); | ||
|
|
||
| try { | ||
| String pong = redisTemplate.execute((RedisCallback<String>) connection -> connection.ping()); | ||
|
|
||
| if ("PONG".equals(pong)) { | ||
| long responseTime = System.currentTimeMillis() - startTime; | ||
| logger.debug("Redis health check: UP ({}ms)", responseTime); | ||
| return true; | ||
| } | ||
| logger.warn("Redis health check: Ping returned unexpected response"); | ||
| return false; | ||
| } catch (Exception e) { | ||
| logger.error("Redis health check failed: {}", e.getMessage()); | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Suraj-kumar00
If Redis/DB has issues, we get failure on hitting /health.
Can we instead return a JSON.
With the current implementation, how do I know which underlying service is broken?
Missed this earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me work on this @drtechie, will update you.