Files
satusehat-service/internal/infrastructure/database/database.text
T
2026-04-14 01:23:34 +00:00

227 lines
7.1 KiB
Plaintext

// internal/infrastructure/database/database.go
package database
import (
"context"
"crypto/tls"
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
"service-general/internal/infrastructure/config"
"service-general/pkg/logger" // Import logger Anda
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
"github.com/lib/pq" // PASTIKAN IMPORT INI ADA
// Driver GORM di-import secara eksplisit untuk Migration
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/logger" // Import logger dari GORM
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// ... (tipe DatabaseType dan interface Service tetap sama)
type service struct {
// GORM DB adalah sumber utama
gormDatabases map[string]*gorm.DB
// Map untuk menyimpan koneksi mentah yang diekstrak dari GORM
sqlDatabases map[string]*sql.DB
sqlxDatabases map[string]*sqlx.DB
mongoClients map[string]*mongo.Client
readReplicas map[string][]*sql.DB
configs map[string]config.DatabaseConfig
readConfigs map[string][]config.DatabaseConfig
mu sync.RWMutex
readBalancer map[string]int
// PERBAIKAN 1: Kembali ke tipe yang benar untuk listener PostgreSQL
listeners map[string]*pq.Listener
listenersMu sync.RWMutex
}
var (
dbManager *service
once sync.Once
)
// ... (fungsi New dan loadFromConfig tetap sama)
// --- PERBAIKAN 2: Buat Adapter untuk Logger GORM ---
// gormLoggerAdapter adalah adapter untuk menghubungkan logger kita dengan GORM
type gormLoggerAdapter struct {
logger logger.Logger
}
// NewGormLoggerAdapter membuat instance baru dari adapter
func NewGormLoggerAdapter(l logger.Logger) logger.Interface {
// Tentukan level log GORM berdasarkan level logger kita
// Ini adalah implementasi sederhana, Anda bisa membuatnya lebih dinamis
logLevel := logger.Silent
if l != nil {
// Anda bisa menambahkan logika untuk menentukan level dari logger Anda
// Untuk sekarang, kita hardcode ke Info
logLevel = logger.Info
}
return &gormLoggerAdapter{
logger: l,
// Konfigurasi logger GORM
Config: logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logLevel,
IgnoreRecordNotFoundError: false,
Colorful: false,
},
}
}
// LogMode menetapkan level log
func (l *gormLoggerAdapter) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *l
newLogger.Config.LogLevel = level
return &newLogger
}
// Info mencatat log info
func (l *gormLoggerAdapter) Info(ctx context.Context, msg string, data ...interface{}) {
l.logger.WithContext(ctx).Info(fmt.Sprintf(msg, data...))
}
// Warn mencatat log warning
func (l *gormLoggerAdapter) Warn(ctx context.Context, msg string, data ...interface{}) {
l.logger.WithContext(ctx).Warn(fmt.Sprintf(msg, data...))
}
// Error mencatat log error
func (l *gormLoggerAdapter) Error(ctx context.Context, msg string, data ...interface{}) {
l.logger.WithContext(ctx).Error(fmt.Sprintf(msg, data...))
}
// Trace mencatat log trace (SQL query)
func (l *gormLoggerAdapter) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
elapsed := time.Since(begin)
sql, rows := fc()
fields := map[string]interface{}{
"duration": elapsed,
"rows": rows,
"sql": sql,
"error": err,
"function": "database.trace",
}
switch {
case err != nil && l.Config.LogLevel >= logger.Error:
l.logger.WithContext(ctx).WithFields(logger.Any("error", err)).Error("Database error", logger.Any("details", fields))
case elapsed > l.Config.SlowThreshold && l.Config.LogLevel >= logger.Warn:
l.logger.WithContext(ctx).Warn("Slow database query", logger.Any("details", fields))
case l.Config.LogLevel == logger.Info:
l.logger.WithContext(ctx).Debug("Database query", logger.Any("details", fields))
}
}
// --- Metode Koneksi GORM (diperbaiki untuk menggunakan adapter) ---
func (s *service) openPostgresGORM(config config.DatabaseConfig) (*gorm.DB, error) {
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Jakarta",
config.Host, config.Username, config.Password, config.Database, config.Port, config.SSLMode)
if config.Schema != "" {
dsn += fmt.Sprintf(" search_path=%s", config.Schema)
}
// PERBAIKAN: Gunakan adapter logger kita
gormConfig := &gorm.Config{
Logger: NewGormLoggerAdapter(logger.Default()),
}
if config.RequireSSL {
// ... (konfigurasi SSL tetap sama)
}
return gorm.Open(postgres.Open(dsn), gormConfig)
}
func (s *service) openMySQLGORM(config config.DatabaseConfig) (*gorm.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.Username, config.Password, config.Host, config.Port, config.Database)
if config.RequireSSL {
dsn += "&tls=true"
}
// PERBAIKAN: Gunakan adapter logger kita
gormConfig := &gorm.Config{
Logger: NewGormLoggerAdapter(logger.Default()),
}
return gorm.Open(mysql.Open(dsn), gormConfig)
}
func (s *service) openSQLServerGORM(config config.DatabaseConfig) (*gorm.DB, error) {
dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s",
config.Username, config.Password, config.Host, config.Port, config.Database)
if config.RequireSSL {
// ... (konfigurasi SSL tetap sama)
}
// PERBAIKAN: Gunakan adapter logger kita
gormConfig := &gorm.Config{
Logger: NewGormLoggerAdapter(logger.Default()),
}
return gorm.Open(sqlserver.Open(dsn), gormConfig)
}
func (s *service) openSQLiteGORM(config config.DatabaseConfig) (*gorm.DB, error) {
// PERBAIKAN: Gunakan adapter logger kita
gormConfig := &gorm.Config{
Logger: NewGormLoggerAdapter(logger.Default()),
}
return gorm.Open(sqlite.Open(config.Path), gormConfig)
}
// --- Metode Lainnya ---
// ... (semua metode lain seperti GetGormDB, GetDB, Migrate, Health, Close, dll. tidak perlu diubah)
// ... (kecuali mungkin bagian Close yang membersihkan listeners)
// Di fungsi Close, pastikan tipe listener sudah benar
func (s *service) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
var errs []error
for name, listener := range s.listeners {
if err := listener.Close(); err != nil {
errs = append(errs, fmt.Errorf("failed to close listener for %s: %w", name, err))
}
}
// ... (sisa kode Close)
}
// Di fungsi ListenForChanges, pastikan Anda menggunakan pq.NewListener
func (s *service) ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(string, string)) error {
// ... (kode Anda sudah benar menggunakan pq.NewListener)
// ...
}