package config import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "log" "os" "strconv" "strings" "time" ) type Config struct { Server ServerConfig Databases map[string]DatabaseConfig ReadReplicas map[string][]DatabaseConfig // For read replicas Keycloak KeycloakConfig Bpjs BpjsConfig SatuSehat SatuSehatConfig } type ServerConfig struct { Port int Mode string } type DatabaseConfig struct { Name string Type string // postgres, mysql, sqlserver, sqlite, mongodb Host string Port int Username string Password string Database string Schema string SSLMode string Path string // For SQLite Options string // Additional connection options MaxOpenConns int // Max open connections MaxIdleConns int // Max idle connections ConnMaxLifetime time.Duration // Connection max lifetime } type KeycloakConfig struct { Issuer string Audience string JwksURL string Enabled bool } type BpjsConfig struct { BaseURL string `json:"base_url"` ConsID string `json:"cons_id"` UserKey string `json:"user_key"` SecretKey string `json:"secret_key"` Timeout time.Duration `json:"timeout"` } type SatuSehatConfig struct { OrgID string `json:"org_id"` FasyakesID string `json:"fasyakes_id"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` AuthURL string `json:"auth_url"` BaseURL string `json:"base_url"` ConsentURL string `json:"consent_url"` KFAURL string `json:"kfa_url"` Timeout time.Duration `json:"timeout"` } // SetHeader generates required headers for BPJS VClaim API func (cfg BpjsConfig) SetHeader() (string, string, string, string, string) { timenow := time.Now().UTC() t, err := time.Parse(time.RFC3339, "1970-01-01T00:00:00Z") if err != nil { log.Fatal(err) } tstamp := timenow.Unix() - t.Unix() secret := []byte(cfg.SecretKey) message := []byte(cfg.ConsID + "&" + fmt.Sprint(tstamp)) hash := hmac.New(sha256.New, secret) hash.Write(message) // to lowercase hexits hex.EncodeToString(hash.Sum(nil)) // to base64 xSignature := base64.StdEncoding.EncodeToString(hash.Sum(nil)) return cfg.ConsID, cfg.SecretKey, cfg.UserKey, fmt.Sprint(tstamp), xSignature } type ConfigBpjs struct { Cons_id string Secret_key string User_key string } // SetHeader for backward compatibility func (cfg ConfigBpjs) SetHeader() (string, string, string, string, string) { bpjsConfig := BpjsConfig{ ConsID: cfg.Cons_id, SecretKey: cfg.Secret_key, UserKey: cfg.User_key, } return bpjsConfig.SetHeader() } func LoadConfig() *Config { config := &Config{ Server: ServerConfig{ Port: getEnvAsInt("PORT", 8080), Mode: getEnv("GIN_MODE", "debug"), }, Databases: make(map[string]DatabaseConfig), ReadReplicas: make(map[string][]DatabaseConfig), Keycloak: KeycloakConfig{ Issuer: getEnv("KEYCLOAK_ISSUER", "https://keycloak.example.com/auth/realms/yourrealm"), Audience: getEnv("KEYCLOAK_AUDIENCE", "your-client-id"), JwksURL: getEnv("KEYCLOAK_JWKS_URL", "https://keycloak.example.com/auth/realms/yourrealm/protocol/openid-connect/certs"), Enabled: getEnvAsBool("KEYCLOAK_ENABLED", true), }, Bpjs: BpjsConfig{ BaseURL: getEnv("BPJS_BASEURL", "https://apijkn.bpjs-kesehatan.go.id"), ConsID: getEnv("BPJS_CONSID", ""), UserKey: getEnv("BPJS_USERKEY", ""), SecretKey: getEnv("BPJS_SECRETKEY", ""), Timeout: parseDuration(getEnv("BPJS_TIMEOUT", "30s")), }, SatuSehat: SatuSehatConfig{ OrgID: getEnv("BRIDGING_SATUSEHAT_ORG_ID", ""), FasyakesID: getEnv("BRIDGING_SATUSEHAT_FASYAKES_ID", ""), ClientID: getEnv("BRIDGING_SATUSEHAT_CLIENT_ID", ""), ClientSecret: getEnv("BRIDGING_SATUSEHAT_CLIENT_SECRET", ""), AuthURL: getEnv("BRIDGING_SATUSEHAT_AUTH_URL", "https://api-satusehat.kemkes.go.id/oauth2/v1"), BaseURL: getEnv("BRIDGING_SATUSEHAT_BASE_URL", "https://api-satusehat.kemkes.go.id/fhir-r4/v1"), ConsentURL: getEnv("BRIDGING_SATUSEHAT_CONSENT_URL", "https://api-satusehat.dto.kemkes.go.id/consent/v1"), KFAURL: getEnv("BRIDGING_SATUSEHAT_KFA_URL", "https://api-satusehat.kemkes.go.id/kfa-v2"), Timeout: parseDuration(getEnv("BRIDGING_SATUSEHAT_TIMEOUT", "30s")), }, } // Load database configurations config.loadDatabaseConfigs() // Load read replica configurations config.loadReadReplicaConfigs() return config } func (c *Config) loadDatabaseConfigs() { // Simplified approach: Directly load from environment variables // This ensures we get the exact values specified in .env // Primary database configuration c.Databases["default"] = DatabaseConfig{ Name: "default", Type: getEnv("DB_CONNECTION", "postgres"), Host: getEnv("DB_HOST", "localhost"), Port: getEnvAsInt("DB_PORT", 5432), Username: getEnv("DB_USERNAME", ""), Password: getEnv("DB_PASSWORD", ""), Database: getEnv("DB_DATABASE", "satu_db"), Schema: getEnv("DB_SCHEMA", "public"), SSLMode: getEnv("DB_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 25), MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 25), ConnMaxLifetime: parseDuration(getEnv("DB_CONN_MAX_LIFETIME", "5m")), } // SATUDATA database configuration c.addPostgreSQLConfigs() // MongoDB database configuration c.addMongoDBConfigs() // Legacy support for backward compatibility envVars := os.Environ() dbConfigs := make(map[string]map[string]string) // Parse database configurations from environment variables for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { continue } key := parts[0] value := parts[1] // Parse specific database configurations if strings.HasSuffix(key, "_CONNECTION") || strings.HasSuffix(key, "_HOST") || strings.HasSuffix(key, "_DATABASE") || strings.HasSuffix(key, "_USERNAME") || strings.HasSuffix(key, "_PASSWORD") || strings.HasSuffix(key, "_PORT") || strings.HasSuffix(key, "_NAME") { segments := strings.Split(key, "_") if len(segments) >= 2 { dbName := strings.ToLower(strings.Join(segments[:len(segments)-1], "_")) property := strings.ToLower(segments[len(segments)-1]) if dbConfigs[dbName] == nil { dbConfigs[dbName] = make(map[string]string) } dbConfigs[dbName][property] = value } } } // Create DatabaseConfig from parsed configurations for additional databases for name, config := range dbConfigs { // Skip empty configurations or system configurations if name == "" || strings.Contains(name, "chrome_crashpad_pipe") || name == "primary" { continue } dbConfig := DatabaseConfig{ Name: name, Type: getEnvFromMap(config, "connection", getEnvFromMap(config, "type", "postgres")), Host: getEnvFromMap(config, "host", "localhost"), Port: getEnvAsIntFromMap(config, "port", 5432), Username: getEnvFromMap(config, "username", ""), Password: getEnvFromMap(config, "password", ""), Database: getEnvFromMap(config, "database", getEnvFromMap(config, "name", name)), Schema: getEnvFromMap(config, "schema", "public"), SSLMode: getEnvFromMap(config, "sslmode", "disable"), Path: getEnvFromMap(config, "path", ""), Options: getEnvFromMap(config, "options", ""), MaxOpenConns: getEnvAsIntFromMap(config, "max_open_conns", 25), MaxIdleConns: getEnvAsIntFromMap(config, "max_idle_conns", 25), ConnMaxLifetime: parseDuration(getEnvFromMap(config, "conn_max_lifetime", "5m")), } // Skip if username is empty and it's not a system config if dbConfig.Username == "" && !strings.HasPrefix(name, "chrome") { continue } c.Databases[name] = dbConfig } } func (c *Config) loadReadReplicaConfigs() { envVars := os.Environ() for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { continue } key := parts[0] value := parts[1] // Parse read replica configurations (format: [DBNAME]_REPLICA_[INDEX]_[PROPERTY]) if strings.Contains(key, "_REPLICA_") { segments := strings.Split(key, "_") if len(segments) >= 5 && strings.ToUpper(segments[2]) == "REPLICA" { dbName := strings.ToLower(segments[1]) replicaIndex := segments[3] property := strings.ToLower(strings.Join(segments[4:], "_")) replicaKey := dbName + "_replica_" + replicaIndex if c.ReadReplicas[dbName] == nil { c.ReadReplicas[dbName] = []DatabaseConfig{} } // Find or create replica config var replicaConfig *DatabaseConfig for i := range c.ReadReplicas[dbName] { if c.ReadReplicas[dbName][i].Name == replicaKey { replicaConfig = &c.ReadReplicas[dbName][i] break } } if replicaConfig == nil { // Create new replica config newConfig := DatabaseConfig{ Name: replicaKey, Type: c.Databases[dbName].Type, Host: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_HOST", c.Databases[dbName].Host), Port: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PORT", c.Databases[dbName].Port), Username: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_USERNAME", c.Databases[dbName].Username), Password: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_PASSWORD", c.Databases[dbName].Password), Database: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_DATABASE", c.Databases[dbName].Database), Schema: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SCHEMA", c.Databases[dbName].Schema), SSLMode: getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_SSLMODE", c.Databases[dbName].SSLMode), MaxOpenConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_OPEN_CONNS", c.Databases[dbName].MaxOpenConns), MaxIdleConns: getEnvAsInt("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_MAX_IDLE_CONNS", c.Databases[dbName].MaxIdleConns), ConnMaxLifetime: parseDuration(getEnv("DB_"+strings.ToUpper(dbName)+"_REPLICA_"+replicaIndex+"_CONN_MAX_LIFETIME", "5m")), } c.ReadReplicas[dbName] = append(c.ReadReplicas[dbName], newConfig) replicaConfig = &c.ReadReplicas[dbName][len(c.ReadReplicas[dbName])-1] } // Update the specific replica switch property { case "host": replicaConfig.Host = value case "port": replicaConfig.Port = getEnvAsInt(key, 5432) case "username": replicaConfig.Username = value case "password": replicaConfig.Password = value case "database": replicaConfig.Database = value case "schema": replicaConfig.Schema = value case "sslmode": replicaConfig.SSLMode = value case "max_open_conns": replicaConfig.MaxOpenConns = getEnvAsInt(key, 25) case "max_idle_conns": replicaConfig.MaxIdleConns = getEnvAsInt(key, 25) case "conn_max_lifetime": replicaConfig.ConnMaxLifetime = parseDuration(value) } } } } } func (c *Config) addSpecificDatabase(prefix, defaultType string) { connection := getEnv(strings.ToUpper(prefix)+"_CONNECTION", defaultType) host := getEnv(strings.ToUpper(prefix)+"_HOST", "") if host != "" { dbConfig := DatabaseConfig{ Name: prefix, Type: connection, Host: host, Port: getEnvAsInt(strings.ToUpper(prefix)+"_PORT", 5432), Username: getEnv(strings.ToUpper(prefix)+"_USERNAME", ""), Password: getEnv(strings.ToUpper(prefix)+"_PASSWORD", ""), Database: getEnv(strings.ToUpper(prefix)+"_DATABASE", getEnv(strings.ToUpper(prefix)+"_NAME", prefix)), Schema: getEnv(strings.ToUpper(prefix)+"_SCHEMA", "public"), SSLMode: getEnv(strings.ToUpper(prefix)+"_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_OPEN_CONNS", 25), MaxIdleConns: getEnvAsInt(strings.ToUpper(prefix)+"_MAX_IDLE_CONNS", 25), ConnMaxLifetime: parseDuration(getEnv(strings.ToUpper(prefix)+"_CONN_MAX_LIFETIME", "5m")), } c.Databases[prefix] = dbConfig } } // PostgreSQL database func (c *Config) addPostgreSQLConfigs() { // SATUDATA database configuration // defaultPOSTGRESHost := getEnv("POSTGRES_HOST", "localhost") // if defaultPOSTGRESHost != "" { // c.Databases["postgres"] = DatabaseConfig{ // Name: "postgres", // Type: getEnv("POSTGRES_CONNECTION", "postgres"), // Host: defaultPOSTGRESHost, // Port: getEnvAsInt("POSTGRES_PORT", 5432), // Username: getEnv("POSTGRES_USERNAME", ""), // Password: getEnv("POSTGRES_PASSWORD", ""), // Database: getEnv("POSTGRES_DATABASE", "postgres"), // Schema: getEnv("POSTGRES_SCHEMA", "public"), // SSLMode: getEnv("POSTGRES_SSLMODE", "disable"), // MaxOpenConns: getEnvAsInt("POSTGRES_MAX_OPEN_CONNS", 25), // MaxIdleConns: getEnvAsInt("POSTGRES_MAX_IDLE_CONNS", 25), // ConnMaxLifetime: parseDuration(getEnv("POSTGRES_CONN_MAX_LIFETIME", "5m")), // } // } // Support for custom PostgreSQL configurations with POSTGRES_ prefix envVars := os.Environ() for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { continue } key := parts[0] // Parse PostgreSQL configurations (format: POSTGRES_[NAME]_[PROPERTY]) if strings.HasPrefix(key, "POSTGRES_") && strings.Contains(key, "_") { segments := strings.Split(key, "_") if len(segments) >= 3 { dbName := strings.ToLower(strings.Join(segments[1:len(segments)-1], "_")) // Skip if it's a standard PostgreSQL configuration if dbName == "connection" || dbName == "dev" || dbName == "default" || dbName == "satudata" { continue } // Create or update PostgreSQL configuration if _, exists := c.Databases[dbName]; !exists { c.Databases[dbName] = DatabaseConfig{ Name: dbName, Type: "postgres", Host: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_HOST", "localhost"), Port: getEnvAsInt("POSTGRES_"+strings.ToUpper(dbName)+"_PORT", 5432), Username: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_USERNAME", ""), Password: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_PASSWORD", ""), Database: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_DATABASE", dbName), Schema: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_SCHEMA", "public"), SSLMode: getEnv("POSTGRES_"+strings.ToUpper(dbName)+"_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("POSTGRES_MAX_OPEN_CONNS", 25), MaxIdleConns: getEnvAsInt("POSTGRES_MAX_IDLE_CONNS", 25), ConnMaxLifetime: parseDuration(getEnv("POSTGRES_CONN_MAX_LIFETIME", "5m")), } } } } } } // addMYSQLConfigs adds MYSQL database func (c *Config) addMySQLConfigs() { // Primary MySQL configuration defaultMySQLHost := getEnv("MYSQL_HOST", "") if defaultMySQLHost != "" { c.Databases["mysql"] = DatabaseConfig{ Name: "mysql", Type: getEnv("MYSQL_CONNECTION", "mysql"), Host: defaultMySQLHost, Port: getEnvAsInt("MYSQL_PORT", 3306), Username: getEnv("MYSQL_USERNAME", ""), Password: getEnv("MYSQL_PASSWORD", ""), Database: getEnv("MYSQL_DATABASE", "mysql"), SSLMode: getEnv("MYSQL_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("MYSQL_MAX_OPEN_CONNS", 25), MaxIdleConns: getEnvAsInt("MYSQL_MAX_IDLE_CONNS", 25), ConnMaxLifetime: parseDuration(getEnv("MYSQL_CONN_MAX_LIFETIME", "5m")), } } // Support for custom MySQL configurations with MYSQL_ prefix envVars := os.Environ() for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { continue } key := parts[0] // Parse MySQL configurations (format: MYSQL_[NAME]_[PROPERTY]) if strings.HasPrefix(key, "MYSQL_") && strings.Contains(key, "_") { segments := strings.Split(key, "_") if len(segments) >= 3 { dbName := strings.ToLower(strings.Join(segments[1:len(segments)-1], "_")) // Skip if it's a standard MySQL configuration if dbName == "connection" || dbName == "dev" || dbName == "max" || dbName == "conn" { continue } // Create or update MySQL configuration if _, exists := c.Databases[dbName]; !exists { mysqlHost := getEnv("MYSQL_"+strings.ToUpper(dbName)+"_HOST", "") if mysqlHost != "" { c.Databases[dbName] = DatabaseConfig{ Name: dbName, Type: getEnv("MYSQL_"+strings.ToUpper(dbName)+"_CONNECTION", "mysql"), Host: mysqlHost, Port: getEnvAsInt("MYSQL_"+strings.ToUpper(dbName)+"_PORT", 3306), Username: getEnv("MYSQL_"+strings.ToUpper(dbName)+"_USERNAME", ""), Password: getEnv("MYSQL_"+strings.ToUpper(dbName)+"_PASSWORD", ""), Database: getEnv("MYSQL_"+strings.ToUpper(dbName)+"_DATABASE", dbName), SSLMode: getEnv("MYSQL_"+strings.ToUpper(dbName)+"_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("MYSQL_MAX_OPEN_CONNS", 25), MaxIdleConns: getEnvAsInt("MYSQL_MAX_IDLE_CONNS", 25), ConnMaxLifetime: parseDuration(getEnv("MYSQL_CONN_MAX_LIFETIME", "5m")), } } } } } } } // addMongoDBConfigs adds MongoDB database configurations from environment variables func (c *Config) addMongoDBConfigs() { // Primary MongoDB configuration mongoHost := getEnv("MONGODB_HOST", "") if mongoHost != "" { c.Databases["mongodb"] = DatabaseConfig{ Name: "mongodb", Type: getEnv("MONGODB_CONNECTION", "mongodb"), Host: mongoHost, Port: getEnvAsInt("MONGODB_PORT", 27017), Username: getEnv("MONGODB_USER", ""), Password: getEnv("MONGODB_PASS", ""), Database: getEnv("MONGODB_MASTER", "master"), SSLMode: getEnv("MONGODB_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("MONGODB_MAX_OPEN_CONNS", 100), MaxIdleConns: getEnvAsInt("MONGODB_MAX_IDLE_CONNS", 10), ConnMaxLifetime: parseDuration(getEnv("MONGODB_CONN_MAX_LIFETIME", "30m")), } } // Additional MongoDB configurations for local database mongoLocalHost := getEnv("MONGODB_LOCAL_HOST", "") if mongoLocalHost != "" { c.Databases["mongodb_local"] = DatabaseConfig{ Name: "mongodb_local", Type: getEnv("MONGODB_CONNECTION", "mongodb"), Host: mongoLocalHost, Port: getEnvAsInt("MONGODB_LOCAL_PORT", 27017), Username: getEnv("MONGODB_LOCAL_USER", ""), Password: getEnv("MONGODB_LOCAL_PASS", ""), Database: getEnv("MONGODB_LOCAL_DB", "local"), SSLMode: getEnv("MONGOD_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("MONGODB_MAX_OPEN_CONNS", 100), MaxIdleConns: getEnvAsInt("MONGODB_MAX_IDLE_CONNS", 10), ConnMaxLifetime: parseDuration(getEnv("MONGODB_CONN_MAX_LIFETIME", "30m")), } } // Support for custom MongoDB configurations with MONGODB_ prefix envVars := os.Environ() for _, envVar := range envVars { parts := strings.SplitN(envVar, "=", 2) if len(parts) != 2 { continue } key := parts[0] // Parse MongoDB configurations (format: MONGODB_[NAME]_[PROPERTY]) if strings.HasPrefix(key, "MONGODB_") && strings.Contains(key, "_") { segments := strings.Split(key, "_") if len(segments) >= 3 { dbName := strings.ToLower(strings.Join(segments[1:len(segments)-1], "_")) // Skip if it's a standard MongoDB configuration if dbName == "connection" || dbName == "dev" || dbName == "local" { continue } // Create or update MongoDB configuration if _, exists := c.Databases[dbName]; !exists { c.Databases[dbName] = DatabaseConfig{ Name: dbName, Type: "mongodb", Host: getEnv("MONGODB_"+strings.ToUpper(dbName)+"_HOST", "localhost"), Port: getEnvAsInt("MONGODB_"+strings.ToUpper(dbName)+"_PORT", 27017), Username: getEnv("MONGODB_"+strings.ToUpper(dbName)+"_USER", ""), Password: getEnv("MONGODB_"+strings.ToUpper(dbName)+"_PASS", ""), Database: getEnv("MONGODB_"+strings.ToUpper(dbName)+"_DB", dbName), SSLMode: getEnv("MONGOD_SSLMODE", "disable"), MaxOpenConns: getEnvAsInt("MONGODB_MAX_OPEN_CONNS", 100), MaxIdleConns: getEnvAsInt("MONGODB_MAX_IDLE_CONNS", 10), ConnMaxLifetime: parseDuration(getEnv("MONGODB_CONN_MAX_LIFETIME", "30m")), } } } } } } func getEnvFromMap(config map[string]string, key, defaultValue string) string { if value, exists := config[key]; exists { return value } return defaultValue } func getEnvAsIntFromMap(config map[string]string, key string, defaultValue int) int { if value, exists := config[key]; exists { if intValue, err := strconv.Atoi(value); err == nil { return intValue } } return defaultValue } func parseDuration(durationStr string) time.Duration { if duration, err := time.ParseDuration(durationStr); err == nil { return duration } return 5 * time.Minute } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvAsInt(key string, defaultValue int) int { valueStr := getEnv(key, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return defaultValue } func getEnvAsBool(key string, defaultValue bool) bool { valueStr := getEnv(key, "") if value, err := strconv.ParseBool(valueStr); err == nil { return value } return defaultValue } func (c *Config) Validate() error { if len(c.Databases) == 0 { log.Fatal("At least one database configuration is required") } for name, db := range c.Databases { if db.Host == "" { log.Fatalf("Database host is required for %s", name) } if db.Username == "" { log.Fatalf("Database username is required for %s", name) } if db.Password == "" { log.Fatalf("Database password is required for %s", name) } if db.Database == "" { log.Fatalf("Database name is required for %s", name) } } if c.Bpjs.BaseURL == "" { log.Fatal("BPJS Base URL is required") } if c.Bpjs.ConsID == "" { log.Fatal("BPJS Consumer ID is required") } if c.Bpjs.UserKey == "" { log.Fatal("BPJS User Key is required") } if c.Bpjs.SecretKey == "" { log.Fatal("BPJS Secret Key is required") } // Validate Keycloak configuration if enabled if c.Keycloak.Enabled { if c.Keycloak.Issuer == "" { log.Fatal("Keycloak issuer is required when Keycloak is enabled") } if c.Keycloak.Audience == "" { log.Fatal("Keycloak audience is required when Keycloak is enabled") } if c.Keycloak.JwksURL == "" { log.Fatal("Keycloak JWKS URL is required when Keycloak is enabled") } } // Validate SatuSehat configuration if c.SatuSehat.OrgID == "" { log.Fatal("SatuSehat Organization ID is required") } if c.SatuSehat.FasyakesID == "" { log.Fatal("SatuSehat Fasyankes ID is required") } if c.SatuSehat.ClientID == "" { log.Fatal("SatuSehat Client ID is required") } if c.SatuSehat.ClientSecret == "" { log.Fatal("SatuSehat Client Secret is required") } if c.SatuSehat.AuthURL == "" { log.Fatal("SatuSehat Auth URL is required") } if c.SatuSehat.BaseURL == "" { log.Fatal("SatuSehat Base URL is required") } return nil }