package data import ( "context" "fmt" "log" "sync" "time" "github.com/google/wire" "github.com/redis/go-redis/v9" "gorm.io/gorm" "kra/internal/conf" ) var ProviderSet = wire.NewSet(NewData, NewAdminRepo, NewSystemRepo, NewAccessRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) type Data struct { mu sync.RWMutex gormDB *gorm.DB redis *redis.Client database *conf.Data_Database config *conf.Data admin *conf.AdminBackend } func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { if c == nil || c.Database == nil { return nil, nil, fmt.Errorf("database configuration is required") } d := &Data{database: c.Database, config: c, admin: admin} db, err := openDatabase(c.Database, false) if err != nil { // The initialization endpoint must remain available when the configured // target database has not been created yet. log.Printf("configured database unavailable before initialization: %v", err) db, err = openFallbackDatabase() if err != nil { return nil, nil, fmt.Errorf("open bootstrap database: %w", err) } } d.gormDB = db if err = db.AutoMigrate(&announcementPO{}, &adminPO{}); err != nil { return nil, nil, fmt.Errorf("migrate bootstrap tables: %w", err) } if c.Redis != nil && c.Redis.Addr != "" { options := &redis.Options{Addr: c.Redis.Addr, Network: c.Redis.Network} if c.Redis.ReadTimeout != nil { options.ReadTimeout = c.Redis.ReadTimeout.AsDuration() } if c.Redis.WriteTimeout != nil { options.WriteTimeout = c.Redis.WriteTimeout.AsDuration() } candidate := redis.NewClient(options) pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond) if pingErr := candidate.Ping(pingCtx).Err(); pingErr != nil { log.Printf("redis unavailable, using in-memory cache: %v", pingErr) _ = candidate.Close() } else { d.redis = candidate } cancel() } cleanup := func() { d.mu.RLock() db := d.gormDB d.mu.RUnlock() if sqlDB, closeErr := db.DB(); closeErr == nil { _ = sqlDB.Close() } if d.redis != nil { _ = d.redis.Close() } } return d, cleanup, nil } func (d *Data) switchDatabase(config *conf.Data_Database) error { db, err := openDatabase(config, true) if err != nil { return err } d.mu.Lock() old := d.gormDB d.gormDB = db d.database = config d.config.Database = config d.mu.Unlock() if err := d.persistConfig(); err != nil { return fmt.Errorf("persist database configuration: %w", err) } if old != nil { if sqlDB, e := old.DB(); e == nil { _ = sqlDB.Close() } } return nil }