diff --git a/apps/carrier-connector/internal/repository/postgres.go b/apps/carrier-connector/internal/repository/postgres.go index 1185abd..526f60d 100644 --- a/apps/carrier-connector/internal/repository/postgres.go +++ b/apps/carrier-connector/internal/repository/postgres.go @@ -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 { diff --git a/apps/carrier-connector/internal/repository/profile.go b/apps/carrier-connector/internal/repository/profile.go index 698706a..c13e4bf 100644 --- a/apps/carrier-connector/internal/repository/profile.go +++ b/apps/carrier-connector/internal/repository/profile.go @@ -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 } diff --git a/apps/carrier-connector/routes.go b/apps/carrier-connector/routes.go index 96a425d..e86808f 100644 --- a/apps/carrier-connector/routes.go +++ b/apps/carrier-connector/routes.go @@ -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", + }, + }) + } +}