package config import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "log" "os" "strconv" "strings" "time" "github.com/go-playground/validator/v10" ) type Config struct { Server ServerConfig Databases map[string]DatabaseConfig ReadReplicas map[string][]DatabaseConfig // For read replicas Keycloak KeycloakConfig Bpjs BpjsConfig SatuSehat SatuSehatConfig Swagger SwaggerConfig WebSocket WebSocketConfig // Tambahkan ini Validator *validator.Validate } type SwaggerConfig struct { Title string Description string Version string TermsOfService string ContactName string ContactURL string ContactEmail string LicenseName string LicenseURL string Host string BasePath string Schemes []string } 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"` } type WebSocketConfig struct { // Timeout configurations ReadTimeout time.Duration `json:"read_timeout"` WriteTimeout time.Duration `json:"write_timeout"` PingInterval time.Duration `json:"ping_interval"` PongTimeout time.Duration `json:"pong_timeout"` HandshakeTimeout time.Duration `json:"handshake_timeout"` // Buffer sizes ReadBufferSize int `json:"read_buffer_size"` WriteBufferSize int `json:"write_buffer_size"` ChannelBufferSize int `json:"channel_buffer_size"` MessageQueueSize int `json:"message_queue_size"` // Connection limits MaxMessageSize int `json:"max_message_size"` QueueWorkers int `json:"queue_workers"` // Monitoring ActivityLogSize int `json:"activity_log_size"` CleanupInterval time.Duration `json:"cleanup_interval"` InactiveTimeout time.Duration `json:"inactive_timeout"` // Server info ServerID string `json:"server_id"` // Features EnableCompression bool `json:"enable_compression"` EnableMetrics bool `json:"enable_metrics"` EnableMonitoring bool `json:"enable_monitoring"` } // 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 // } 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{ // Configuration for the server Server: ServerConfig{ Port: getEnvAsInt("PORT", 8080), Mode: getEnv("GIN_MODE", "debug"), }, // Configuration for the database Databases: make(map[string]DatabaseConfig), // Configuration for read replicas ReadReplicas: make(map[string][]DatabaseConfig), // Configuration for Keycloak authentication and authorization 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), }, // Configuration for BPJS service bridging API 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")), }, // Configuration for Satu Sehat service bridging API 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")), }, // Configuration for WebSocket server WebSocket: WebSocketConfig{ // Tambahkan ini // Timeout configurations ReadTimeout: parseDuration(getEnv("WS_READ_TIMEOUT", "300s")), WriteTimeout: parseDuration(getEnv("WS_WRITE_TIMEOUT", "30s")), PingInterval: parseDuration(getEnv("WS_PING_INTERVAL", "60s")), PongTimeout: parseDuration(getEnv("WS_PONG_TIMEOUT", "70s")), HandshakeTimeout: parseDuration(getEnv("WS_HANDSHAKE_TIMEOUT", "45s")), // Buffer sizes ReadBufferSize: getEnvAsInt("WS_READ_BUFFER_SIZE", 8192), WriteBufferSize: getEnvAsInt("WS_WRITE_BUFFER_SIZE", 8192), ChannelBufferSize: getEnvAsInt("WS_CHANNEL_BUFFER_SIZE", 512), MessageQueueSize: getEnvAsInt("WS_MESSAGE_QUEUE_SIZE", 5000), // Connection limits MaxMessageSize: getEnvAsInt("WS_MAX_MESSAGE_SIZE", 8192), QueueWorkers: getEnvAsInt("WS_QUEUE_WORKERS", 10), // Monitoring ActivityLogSize: getEnvAsInt("WS_ACTIVITY_LOG_SIZE", 1000), CleanupInterval: parseDuration(getEnv("WS_CLEANUP_INTERVAL", "2m")), InactiveTimeout: parseDuration(getEnv("WS_INACTIVE_TIMEOUT", "5m")), // Server info ServerID: getEnv("WS_SERVER_ID", "api-service-v1"), // Features EnableCompression: getEnvAsBool("WS_ENABLE_COMPRESSION", true), EnableMetrics: getEnvAsBool("WS_ENABLE_METRICS", true), EnableMonitoring: getEnvAsBool("WS_ENABLE_MONITORING", true), }, // Configuration for Swagger Swagger: SwaggerConfig{ Title: getEnv("SWAGGER_TITLE", "SERVICE API"), Description: getEnv("SWAGGER_DESCRIPTION", "CUSTUM SERVICE API"), Version: getEnv("SWAGGER_VERSION", "1.0.0"), TermsOfService: getEnv("SWAGGER_TERMS_OF_SERVICE", "http://swagger.io/terms/"), ContactName: getEnv("SWAGGER_CONTACT_NAME", "API Support"), ContactURL: getEnv("SWAGGER_CONTACT_URL", "http://rssa.example.com/support"), ContactEmail: getEnv("SWAGGER_CONTACT_EMAIL", "support@swagger.io"), LicenseName: getEnv("SWAGGER_LICENSE_NAME", "Apache 2.0"), LicenseURL: getEnv("SWAGGER_LICENSE_URL", "http://www.apache.org/licenses/LICENSE-2.0.html"), Host: getEnv("SWAGGER_HOST", "localhost:8080"), BasePath: getEnv("SWAGGER_BASE_PATH", "/api/v1"), Schemes: parseSchemes(getEnv("SWAGGER_SCHEMES", "http,https")), }, } // Initialize validator config.Validator = validator.New() // 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 } // parseSchemes parses comma-separated schemes string into a slice func parseSchemes(schemesStr string) []string { if schemesStr == "" { return []string{"http"} } schemes := strings.Split(schemesStr, ",") for i, scheme := range schemes { schemes[i] = strings.TrimSpace(scheme) } return schemes } 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") } // Validate WebSocket configuration if c.WebSocket.ReadTimeout <= 0 { log.Fatal("WebSocket Read Timeout must be greater than 0") } if c.WebSocket.WriteTimeout <= 0 { log.Fatal("WebSocket Write Timeout must be greater than 0") } if c.WebSocket.PingInterval <= 0 { log.Fatal("WebSocket Ping Interval must be greater than 0") } if c.WebSocket.PongTimeout <= 0 { log.Fatal("WebSocket Pong Timeout must be greater than 0") } if c.WebSocket.ReadBufferSize <= 0 { log.Fatal("WebSocket Read Buffer Size must be greater than 0") } if c.WebSocket.WriteBufferSize <= 0 { log.Fatal("WebSocket Write Buffer Size must be greater than 0") } if c.WebSocket.ChannelBufferSize <= 0 { log.Fatal("WebSocket Channel Buffer Size must be greater than 0") } if c.WebSocket.MessageQueueSize <= 0 { log.Fatal("WebSocket Message Queue Size must be greater than 0") } if c.WebSocket.MaxMessageSize <= 0 { log.Fatal("WebSocket Max Message Size must be greater than 0") } if c.WebSocket.QueueWorkers <= 0 { log.Fatal("WebSocket Queue Workers must be greater than 0") } if c.WebSocket.ActivityLogSize <= 0 { log.Fatal("WebSocket Activity Log Size must be greater than 0") } if c.WebSocket.CleanupInterval <= 0 { log.Fatal("WebSocket Cleanup Interval must be greater than 0") } if c.WebSocket.InactiveTimeout <= 0 { log.Fatal("WebSocket Inactive Timeout must be greater than 0") } if c.WebSocket.ServerID == "" { log.Fatal("WebSocket Server ID is required") } return nil } //** WebSocket **// // DefaultWebSocketConfig mengembalikan konfigurasi default untuk WebSocket func DefaultWebSocketConfig() WebSocketConfig { return WebSocketConfig{ // Timeout configurations ReadTimeout: 300 * time.Second, WriteTimeout: 30 * time.Second, PingInterval: 60 * time.Second, PongTimeout: 70 * time.Second, HandshakeTimeout: 45 * time.Second, // Buffer sizes ReadBufferSize: 8192, WriteBufferSize: 8192, ChannelBufferSize: 512, MessageQueueSize: 5000, // Connection limits MaxMessageSize: 8192, QueueWorkers: 10, // Monitoring ActivityLogSize: 1000, CleanupInterval: 2 * time.Minute, InactiveTimeout: 5 * time.Minute, // Server info ServerID: "api-service-v1", // Features EnableCompression: true, EnableMetrics: true, EnableMonitoring: true, } } // HighPerformanceWebSocketConfig mengembalikan konfigurasi untuk performa tinggi func HighPerformanceWebSocketConfig() WebSocketConfig { return WebSocketConfig{ // Timeout configurations ReadTimeout: 300 * time.Second, WriteTimeout: 30 * time.Second, PingInterval: 30 * time.Second, PongTimeout: 40 * time.Second, HandshakeTimeout: 30 * time.Second, // Buffer sizes ReadBufferSize: 16384, WriteBufferSize: 16384, ChannelBufferSize: 1024, MessageQueueSize: 10000, // Connection limits MaxMessageSize: 16384, QueueWorkers: 20, // Monitoring ActivityLogSize: 2000, CleanupInterval: 1 * time.Minute, InactiveTimeout: 3 * time.Minute, // Server info ServerID: "api-service-hp", // Features EnableCompression: true, EnableMetrics: true, EnableMonitoring: true, } } // LowResourceWebSocketConfig mengembalikan konfigurasi untuk sumber daya terbatas func LowResourceWebSocketConfig() WebSocketConfig { return WebSocketConfig{ // Timeout configurations ReadTimeout: 300 * time.Second, WriteTimeout: 30 * time.Second, PingInterval: 120 * time.Second, PongTimeout: 130 * time.Second, HandshakeTimeout: 60 * time.Second, // Buffer sizes ReadBufferSize: 4096, WriteBufferSize: 4096, ChannelBufferSize: 256, MessageQueueSize: 2500, // Connection limits MaxMessageSize: 4096, QueueWorkers: 5, // Monitoring ActivityLogSize: 500, CleanupInterval: 5 * time.Minute, InactiveTimeout: 10 * time.Minute, // Server info ServerID: "api-service-lr", // Features EnableCompression: false, EnableMetrics: false, EnableMonitoring: true, } } // CustomWebSocketConfig memungkinkan kustomisasi konfigurasi func CustomWebSocketConfig( readTimeout, writeTimeout, pingInterval, pongTimeout time.Duration, readBufferSize, writeBufferSize, channelBufferSize, messageQueueSize int, maxMessageSize, queueWorkers int, activityLogSize int, cleanupInterval, inactiveTimeout time.Duration, serverID string, enableCompression, enableMetrics, enableMonitoring bool, ) WebSocketConfig { return WebSocketConfig{ ReadTimeout: readTimeout, WriteTimeout: writeTimeout, PingInterval: pingInterval, PongTimeout: pongTimeout, HandshakeTimeout: 45 * time.Second, ReadBufferSize: readBufferSize, WriteBufferSize: writeBufferSize, ChannelBufferSize: channelBufferSize, MessageQueueSize: messageQueueSize, MaxMessageSize: maxMessageSize, QueueWorkers: queueWorkers, ActivityLogSize: activityLogSize, CleanupInterval: cleanupInterval, InactiveTimeout: inactiveTimeout, ServerID: serverID, EnableCompression: enableCompression, EnableMetrics: enableMetrics, EnableMonitoring: enableMonitoring, } }