1000 lines
34 KiB
Go
1000 lines
34 KiB
Go
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,
|
|
}
|
|
}
|