package databaseconfig import ( "context" "database/sql" "errors" "fmt" "net" "strconv" "strings" "time" mysql "github.com/go-sql-driver/mysql" "primaya-api/cpone-middleware/internal/repository" ) // ErrRSCodeMismatch indicates that a request was sent to the wrong hospital // instance. The code is a guard for this installation, not a database key. var ErrRSCodeMismatch = errors.New("kode RS tidak sesuai dengan instance CPONE") type Setting struct { RSCode string `json:"kode_rs"` Name string `json:"nama"` Host string `json:"host"` Port string `json:"port"` Database string `json:"database"` Username string `json:"username"` Password string `json:"password"` } type PoolConfig struct { MaxOpenConns int MaxIdleConns int ConnMaxLifetime time.Duration } func NormalizeRSCode(code string) string { return strings.ToUpper(strings.TrimSpace(code)) } // StaticResolver owns one HIS connection for one CPONE installation. Resolve // only validates the request code and always returns the same repository. type StaticResolver struct { code string db *sql.DB repo repository.MySQLLayananRepository } func NewStaticResolver(ctx context.Context, code string, setting Setting, pool PoolConfig) (*StaticResolver, error) { code = NormalizeRSCode(code) if code == "" { return nil, errors.New("CPONE_RS_CODE wajib diisi") } if setting.Host == "" || setting.Port == "" || setting.Database == "" || setting.Username == "" { return nil, errors.New("host, port, database, dan username HIS wajib diisi") } port, err := strconv.Atoi(setting.Port) if err != nil || port < 1 || port > 65535 { return nil, errors.New("port database HIS harus berupa angka antara 1 sampai 65535") } db, err := sql.Open("mysql", mysqlDSN(setting)) if err != nil { return nil, err } applyPoolConfig(db, pool) if err := db.PingContext(ctx); err != nil { _ = db.Close() return nil, fmt.Errorf("koneksi database HIS gagal: %w", err) } return &StaticResolver{code: code, db: db, repo: repository.NewMySQLLayananRepository(db)}, nil } func (r *StaticResolver) Resolve(_ context.Context, code string) (repository.MySQLLayananRepository, error) { if NormalizeRSCode(code) != r.code { return repository.MySQLLayananRepository{}, ErrRSCodeMismatch } return r.repo, nil } func (r *StaticResolver) Close() error { if r == nil || r.db == nil { return nil } return r.db.Close() } func (r *StaticResolver) Code() string { return strings.TrimSpace(r.code) } func applyPoolConfig(db *sql.DB, pool PoolConfig) { db.SetMaxOpenConns(pool.MaxOpenConns) db.SetMaxIdleConns(pool.MaxIdleConns) db.SetConnMaxLifetime(pool.ConnMaxLifetime) } func mysqlDSN(setting Setting) string { cfg := mysql.NewConfig() cfg.User = setting.Username cfg.Passwd = setting.Password cfg.Net = "tcp" cfg.Addr = net.JoinHostPort(setting.Host, setting.Port) cfg.DBName = setting.Database cfg.ParseTime = true cfg.Loc = time.Local cfg.Params = map[string]string{"charset": "utf8mb4"} return cfg.FormatDSN() }