feat: Add liveness and readiness health check endpoints with database connectivity validation

- Add Ping method to PostgresProfileStore to check database connection status
- Add Ping method to ProfileRepository interface
- Add /api/v1/health/live endpoint for liveness checks (always returns alive if service is running)
- Add /api/v1/health/ready endpoint for readiness checks with database connectivity validation
- Add readinessHandler that returns SERVICE_UNAVAILABLE status when database ping fails
This commit is contained in:
nutcas3
2026-04-25 03:23:01 +03:00
parent 05eb31e117
commit 8e40a54d8b
3 changed files with 50 additions and 3 deletions
@@ -144,6 +144,15 @@ func (s *PostgresProfileStore) Close() error {
return sqlDB.Close()
}
// Ping checks if the database connection is alive.
func (s *PostgresProfileStore) Ping() error {
sqlDB, err := s.db.DB()
if err != nil {
return err
}
return sqlDB.Ping()
}
// toRow maps domain -> row. Passthrough of CreatedAt/UpdatedAt lets GORM hooks
// populate them on first save.
func toRow(p *Profile) *profileRow {
@@ -1,9 +1,9 @@
package repository
import (
"context"
"errors"
"time"
"context"
"errors"
"time"
)
// ErrNotFound is returned when a profile lookup misses.
@@ -39,4 +39,5 @@ type ProfileRepository interface {
List(ctx context.Context, f ListFilter) ([]*Profile, int, error)
UpdateState(ctx context.Context, iccid, state string) (*Profile, error)
Delete(ctx context.Context, iccid string) error
Ping() error
}
+37
View File
@@ -15,6 +15,8 @@ import (
func setupRoutes(router *gin.Engine, client *es2.ES2Client, repo repository.ProfileRepository) {
api := router.Group("/api/v1")
api.GET("/health", healthHandler)
api.GET("/health/ready", readinessHandler(repo))
api.GET("/health/live", livenessHandler)
esim := api.Group("/esim")
{
@@ -39,3 +41,38 @@ func healthHandler(c *gin.Context) {
"timestamp": time.Now().UTC(),
})
}
// livenessHandler returns a simple liveness check (always healthy if service is running).
func livenessHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "alive",
"service": "carrier-connector",
"timestamp": time.Now().UTC(),
})
}
// readinessHandler checks if the service is ready to accept requests (database connectivity).
func readinessHandler(repo repository.ProfileRepository) gin.HandlerFunc {
return func(c *gin.Context) {
// Check database connectivity
if err := repo.Ping(); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{
"status": "not ready",
"service": "carrier-connector",
"timestamp": time.Now().UTC(),
"error": "database connection failed",
"details": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "ready",
"service": "carrier-connector",
"timestamp": time.Now().UTC(),
"checks": gin.H{
"database": "ok",
},
})
}
}